COMP6771 Advanced C++ Programming
Transcript of COMP6771 Advanced C++ Programming
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
COMP6771Advanced C++ Programming
Week 4Part One: Copy Control (continued) and Move Semantics
2016
www.cse.unsw.edu.au/˜cs6771
1
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Inline Constructors, Accessors and Mutators
Question (from 2015): In the week 3 examples, constructorsand getters/setters were defined inside the class delcaration.However, we’ve been told to seperate declarations (.h) anddefinitions (.cpp).
2
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Inline Constructors, Accessors and Mutators
Answer: Remember the .h file, is the “public” interface, soeveryone can see all the function declarations and datamembers (both public and private).
Your class’ data members are not secret, but how they areused might be.
Therefore, the specific code implementation of methodsshould be in the .cpp file.
However, simple constructors, getters, and setters that are notcomplex may be inlined/defined in the class declaration.
See also: http://google-styleguide.googlecode.com/svn/trunk/cppguide.html#Inline_Functions
3
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Copy Control and Resource Management
Use copy-control members to management resources (e.g.,memory, file and socket handles)
Two general strategies:Value-like classes (with value/copy semantics)
Class data members have their own stateWhen an object is copied, the copy and the original areindependent of each other
Pointer-like classes (with reference/pointer semantics)
Class data members share stateWhen an object is copied, the copy and the original use thesame underlying dataChanges made to the copy also affect the original, and viceversa
.
......Let us write a value-like stack class.
4
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Interface: UB stack.h
1 #ifndef UB_STACK_H2 #define UB_STACK_H34 class UB_stack {5 public:6 UB_stack(); // default constructor7 UB_stack(const UB_stack &s); // copy constructor8 UB_stack(UB_stack &&s); // move constructor9 ˜UB_stack(); // destructor
10 UB_stack& operator=(const UB_stack &s); // copy assignment11 UB_stack& operator=(UB_stack &&s); // move assignment12 void push(const int &item);13 void pop();14 int& top();15 const int& top() const;16 bool empty() const;17 bool full() const;18 private:19 class Node; // forward declaration of nested class20 void reverse(Node *); // private method21 Node *head_;2223 friend void swap(UB_stack &s1, UB_stack &s2);24 };2526 void swap(UB_stack &s1, UB_stack &s2);2728 #endif /* UB_STACK_H */
5
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Implementation: UB stack.cpp I
1 #include "UB_stack.h"2
3 // nested class4 class UB_stack::Node {5 // allow UB_stack to access private data6 friend class UB_stack;7 // methods and data in this class are private by default8 Node(int i, Node *n = nullptr) : item_{i}, next_{n} { }9 ˜Node() { delete next_; } // destructor cleans up the memory
10
11 int item_;12 Node *next_;13 };14
15 // constructor16 UB_stack::UB_stack() : head_{nullptr} { }17
18 // copy constructor19 UB_stack::UB_stack(const UB_stack &s) : head_{nullptr} {20 reverse(s.head_);21 }
6
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Implementation: UB stack.cpp II22 // destructor23 UB_stack::˜UB_stack() { delete head_; }24
25 // return the top of the stack26 int& UB_stack::top() { return head_->item_; }27 const int& UB_stack::top() const { return head_->item_; }28 bool UB_stack::empty() const return head_ == 0;29 bool UB_stack::full() const return false; ;30
31 // method to work down a given stack32 // and push items onto "this" stack correctly33 void UB_stack::reverse(Node *h) {34 if (h != nullptr) {35 reverse(h->next_);36 push(h->item_);37 }38 }39
40 // method to push an int onto the stack41 void UB_stack::push(const int &item) {42 head_ = new Node (item, head_);43 }
7
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Implementation: UB stack.cpp III44
45 // copy assignment operator46 UB_stack& UB_stack::operator=(const UB_stack &s) {47 // if not already the same stack48 if (this != &s) {49 delete head_; // delete this stack50 head_ = nullptr;51 reverse(s.head_); // copy data from other stack52 }53 return *this;54 }55
56 // pop off the top of the stack57 void UB_stack::pop() {58 Node *t = head_;59 head_ = head_->next_;60 t->next_ = nullptr;61 delete t;62 }63
64
65
8
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Implementation: UB stack.cpp IV66 // return the top of the stack67 int& UB_stack::top() { return head_->item_; }68 const int& UB_stack::top() const { return head_->item_; }69
70 bool UB_stack::empty() const { return head_ == 0; }71 bool UB_stack::full() const { return false; };72
73 // move constructor74 UB_stack::UB_stack(UB_stack &&s) : head_{s.head_} {75 s.head_ = nullptr;76 }77
78 // move assignment79 UB_stack& UB_stack::operator=(UB_stack &&s) {80 if (this != &s) {81 delete head_;82 head_ = s.head_;83 s.head_ = nullptr;84 }85 return *this;86 }87
9
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Implementation: UB stack.cpp V
88 void swap(UB_stack &s1, UB_stack &s2) {89 using std::swap;90 // swap the pointers to the heads of the list only91 // much faster than swapping all the data92 swap(s1.head_, s2.head_); // call std::swap on the pointers93 }
10
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Client Code
1 #include <iostream>2 #include "UB_stack.h"3
4 int main() {5 UB_stack s;6 s.push(1);7 s.push(2);8 s.push(3);9 s.push(4);
10 s.pop();11 std::cout << s.top() << std::endl;12 // ...13 }
11
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
The Compiler-Generated Destructor vs. Ours1 UB_stack::˜UB_stack() { } // compiler generated destructor
Client code:
1 { // new scope2 UB_stack s;3 s.push(1);4 s.push(2);5 s.push(3);6 s.push(4);7 } // ˜UB_stack()
heads
runtime stack heap
next next next next
4 3 2 1
At the closing scope }:...1 The destructor is called: do nothing...2 The members’ destructors are called: do nothing
The stack object has leaked!
Our destructor works as expected (see code).
12
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
The Compiler-Generated Copy Constructor
Shallow copy:
1 UB_stack::UB_stack(const UB_stack &s) : head_{s.head_} { }
Example:
UB_stack s2 {s1}; // copy construction
Before
heads1
runtime stack heap
next next next next4 3 2 1
After
heads1
heads2runtime stack heap
next next next next4 3 2 1
Failed to provide value-like semantics
Potentially lead to memory corruption errors!
13
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Our Copy Constructor
Deep copy:
1 UB_stack::UB_stack(const UB_stack &s) : head_{nullptr} {2 reverse(s.head_);3 }
Example:
UB_stack s2 {s1}; // copy construction
Before
heads1
runtime stack heap
next next next next4 3 2 1
After
heads1
runtime stack heap
heads2
next next next next4 3 2 1
next next next next4 3 2 1
14
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
The Compiler-Generated Copy operator=Shallow copy:
1 UB_stack& UB_stack::operator=(const UB_stack &s) {2 head_ = s.head_;3 return *this;4 }
Example:
s2 = s1;
Before
heads1
runtime stack heap
heads2
next next next next
next next next next
4 3 2 1
5 5 5 5
After
heads1
runtime stack heap
heads2
next next next next
next next next next
4 3 2 1
5 5 5 5
Failed to provide value-like semanticsPotentially lead to memory corruption errors!
.
......The memory pointed by s2.head has leaked!15
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Our Copy operator=Deep copy:
1 UB_stack& UB_stack::operator=(const UB_stack &s) {2 if (this != &s) {3 delete head_;4 head_ = nullptr;5 reverse(s.head_);6 }7 return *this;8 }
Example:
s2 = s1;
Before
heads1
runtime stack heap
heads2
next next next next
next next next next
4 3 2 1
5 5 5 5
After
heads1
runtime stack heap
heads2
next next next next
next next next next
4 3 2 1
4 3 2 1
.
......The memory pointed by s2.head before has been freed!16
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Limitations of Copy Semantics
Copy constructor called on the returned object of anon-reference type:
1 UB_stack makeStack () {2 UB_stack s;3 int i;4 while (cin >> i)5 s.push(i);6 return s;7 }8
9 UB_stack s1 = makeStack();
The compiler may optimise some calls to copy ctors away.
17
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Move Semantics
Using swap:
1 void stack_swap(UB_stack &s1, UB_stack &s2) {2 UB_stack tmp = s1; // copy constructor3 s1 = s2; // copy assignment4 s2 = tmp; // copy assignment5 } // destructor for tmp
Can we simply swap the internal resources in s1 and s2?
Yes, we can in C++11:Understand lvalue reference (&) and rvalue references (&&)Understand the move semantics
.Move Semantics (for Improved Performance)..
......
The move semantics allows you to avoid unnecessary copies whenworking with temporary objects that are about to evaporate, andwhose resources can safely be taken from that such a temporaryobject and used by another.
18
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Why Move Semantics?
Can we do copy construction/initialisation efficiently?
1 Sales_data src;2 Sales_data dst = src;
Copy src into dst if src persists, i.e., will be used again orMove the internal resources of src into dst if src is atemporary object, i.e., one that will be destroyed or assigned to
Can we also perform assignment efficiently?
1 Sales_data dst;2 dst = src;
...1 Destroy dst
...2 Assign to dst:
Copy src into dst if src persists, i.e., will be used again orMove the internal resources of src into dst if src is atemporary object, i.e., one that will be destroyed or assigned to
19
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Interface (+ Move Semantics): UB stack.h
1 class UB_stack {2 public:3 // copy constructor4 UB_stack(const UB_stack &s);5 // move constructor6 UB_stack(UB_stack &&s);7
8 UB_stack& operator=(const UB_stack &s);9 // move assignment
10 UB_stack& operator=(UB_stack &&s);11
12 ...13 };
By distinguishing lvalues references from rvalue references:
Copy and move constructors are overloaded
Copy and move assignment operators are also overloaded
20
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Implementation (+ Move Semantics): UB stack.cpp
1 #include "UB_stack.h"2 // move constructor3 UB_stack::UB_stack(UB_stack &&s) : head_{std::move(s.head_)} {4 s.head_ = nullptr;5 }6
7 // move assignment8 UB_stack& UB_stack::operator=(UB_stack &&s) {9 if (this != &s) {
10 delete head_;11 head_ = std::move(s.head_);12 s.head_ = nullptr;13 }14 return *this;15 }
.
......
After the “resources” have been stolen, i.e., from the moved-fromobject, its data members must be modified in order to put it in avalid state (to be destroyed by its destructor).
21
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
The Synthesised Move Constructor/Assignment
Synthesised only if none of the Big Three is provided
Move constructor/assignment: member-wise move
Call a member’s move constructor/assignment to moveThe members of built-int types are copied directlyArray members are copied by copying each element
The synthesised solutions for UB stack are wrong:
1 #include "UB_stack.h"2 // move constructor3 UB_stack::UB_stack(UB_stack &&s) : head_{std::move(s.head_)}4 noexcept { }5 // move assignment6 UB_stack& UB_stack::operator=(UB_stack &&s) noexcept {7 head_ = std::move(s.head_);8 return *this;9 }
22
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
The Compiler-Generated Move ConstructorThe same as when the synthesised copy constructor is used
Stealing from the Moved-From Object:1 UB_stack::UB_stack(UB_stack &&s) noexcept :2 head_{std::move(s.head_)} { }
Example:
UB_stack s2 = std::move(s1);
Before
heads1
runtime stack heap
next next next next4 3 2 1
After
heads1
heads2runtime stack heap
next next next next4 3 2 1
When the moved-from object dies, the destructor for it iscalled. The commonly shared stack will be freed=⇒ s2.head points to something that has been freed!
23
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Our Move Constructor
Move Semantics:1 UB_stack::UB_stack(UB_stack &&s) : head_{std::move(s.head_)} {2 s.head = nullptr; // put the moved-from object3 } // in a valid state to be destroyed
Example:
UB_stack s2 = std::move(s1);
Before
heads1
runtime stack heap
next next next next4 3 2 1
After
heads1
nullptr
runtime stack heap
heads2
next next next next4 3 2 1
24
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
The Compiler-Generated Move operator=The same as when the synthesised Copy operator= is used
Stealing from the moved-from object:1 UB_stack& UB_stack::operator=(UB_stack &&s) noexcept {2 head_ = std::move(s.head_);3 return *this;4 }
Example: s2 = std::move(s1);
Before
heads1
runtime stack heap
heads2
next next next next
next next next next
4 3 2 1
5 5 5 5
After
heads1
runtime stack heap
heads2
next next next next
next next next next
4 3 2 1
5 5 5 5
Failed to provide value-like semanticsPotentially lead to memory corruption errors!
.
......The memory pointed by s2.head has leaked!25
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Our Move operator=1 UB_stack& UB_stack::operator=(UB_stack &&s) {2 if (this != &s) {3 delete head_;4 head_ = std::move(s.head_);5 s.head_ = nullptr;6 }7 return *this;8 }
Example: s2 = std::move(s1);
Before
heads1
runtime stack heap
heads2
next next next next
next next next next
4 3 2 1
5 5 5 5
After
heads1
nullptr
runtime stack heap
heads2
next next next next4 3 2 1
.
......The memory pointed by s2.head before has been freed!26
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
noexcept
Signals to the compiler and optimiser that no exception willbe thrown from the method.
Automatically provided for compiler synthesised big five.
Also: http://en.cppreference.com/w/cpp/language/noexcept_spec
27
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Lvalues and Rvalues Revisited
Lvalues: an object’s identity or address:
1 int i = 1; // i is an lvalue2 int& getRef() {3 return i;4 }5 getRef() += 1; // getRef() is an lvalue6 // i = 2
Rvalues: an object’s value
1 int getVal() {2 int i = 0;3 i = i + 1; // i + 1 is an rvalue4 return i;5 }6 getVal() += 1; // error since getVal() is an rvalue
28
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Lvalue and Rvalue References
An lvalue reference is formed by placing an & after some type:
1 A a;2 A& a_ref1 = a; // an lvalue reference
An rvalue reference is formed by placing an && after sometype:
1 A a;2 A&& a_ref2 = a + a; // an rvalue reference
An rvalue reference behaves just like an lvalue referenceexcept that it can bind to a temporary (an rvalue), whereasyou can not bind a (non const) lvalue reference to an rvalue.
1 A& a_ref3 = A{}; // error!2 A&& a_ref4 = A{}; // ok
29
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Lvalue vs. Rvalue References (Cont’d)
In general
Lvalue references are persistent – every variable is an lvaluereference
Rvalue references are bound to objects that
are about to be destroyed, anddon’t have any other user any more.
Rvalue references identify temporary objects.
30
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Example Lvalue and Rvalue References
Lvalue references:
Functions that return lvalue references++i*pa[2]
Rvalue references:
Functions that return non-reference typesi++i + ji < k
where the result in each case will be stored in acompiler-generated temporary object.
31
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Our stack swap for UB stack
The one written for copy semantics:
1 void stack_swap(UB_stack &s1, UB_stack &s2) {2 UB_stack tmp = s1; // copy constructor3 s1 = s2; // copy assignment4 s2 = tmp; // copy assignment5 }
The one written for move semantics:1 void stack_swap(UB_stack &s1, UB_stack &s2) {2 UB_stack tmp = std::move(s1); // move constructor3 s1 = std::move(s2); // move assignment4 s2 = std::move(tmp); // move assignment5 }
Every variable/lvalue reference/rvalue reference is an lvalue
std::move converts its argument into an rvalue reference sothat the move-related copy-control members can be called.
std::move is a potentially destructive read
32
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
swap in the C++ Library
No need to write stack swap. There is one in the C++ library.
1 template<class T>2 void swap(T& a, T& b) {3 T tmp = std::move(a);4 a = std::move(b);5 b = std::move(tmp);6 }
33
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Argument-Dependent Lookup (ADL)1 namespace A {2 struct X { };3 void f(const X&) {4 }5 }6
7 int main() {8 A::X x;9 f(x); SAME as A::f(x)
10 }
...1 First, the normal name lookup for f is performed
...2 Then, look for f in the namespace scope where x is defined..Why ADL........http://www.gotw.ca/publications/mill08.htm
.Criticisms..
......
http://en.wikipedia.org/wiki/Argument-dependent_name_
lookup#Criticism34
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Interface (+ Specialised swap): UB stack.h
1 class UB_stack {2 friend void swap(UB_stack &s1, UB_stack &s2);3 public:4 // copy constructor5 UB_stack(const UB_stack &s);6 // move constructor7 UB_stack(UB_stack &&s);8
9 UB_stack& operator=(const UB_stack &s);10 // move assignment11 UB_stack& operator=(UB_stack &&s);12
13 ...14 };15 // the declaration is needed still16 void swap(UB_stack &s1, UB_stack &s2);
.
......Provides a specialised, faster version than std::swap
35
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Implementation (+ Specialised swap): UB stack.cpp
1 #include "UB_stack.h"2
3 ...4
5 void swap(UB_stack &s1, UB_stack &s2) {6 using std::swap;7 // swap the pointers to the heads of the list only8 // much faster than swapping all the data9 swap(s1.head_, s2.head_); // call std::swap on the pointers
10 }11
12 ...13
The using std::swap is important:
Use a type-specific version of swap via ADL if it exists
Otherwise, use the one from using std::swap
Carefully read §7.3.
36
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Some Advice
Use STL containers whenever possible as you don’t have toworry about copy control – done for you (Assignment 1)
Sometimes, you need to write your own containers
If you want your class to behave like a value, you need tomanage your own copy control (Assignment 2)If you want your class to behave like a pointer, you can useand/or develop smarter pointers with reference counting(covered later)
37
. . ..... . . . . . . .Copy Control
. . . . . . . . . .Move Semantics
. . . . . .Lvalues and Rvalues
. . . . .ADL
Readings
Chapter 13
Rvalue references:http://thbecker.net/articles/rvalue_references/
section_01.html
Move semantics:http:
//www.drdobbs.com/move-constructors/184403855
Will look at perfect forwarding when we learn how to writefunction and class templates
Will have a chance to practice the Big Five in Assignment 2
38