1、The Copy Constructor
What is a copy constructor? When is it used?
A copy constructor is a constructor which first parameter is a reference to the class type and any additional parameters have
default values.
When copy initialization happens and that copy initialization requires either the copy constructor or the move constructor
- Define variables using an
=
- Pass an object as an argument to a parameter of non-reference type
- Return an object from a function that has a non-reference return type
- Brace initialize the elements in an array or the members of an aggregate class
- Some class types also use copy initialization for the objects they allocate.
Point global;
Point foo_bar(Point arg) // 1
{
Point local = arg, *heap = new Point(global); // 2, 3
*heap = local;
Point pa[ 4 ] = { local, *heap }; // 4, 5
return *heap; // 6
}
2、The Copy-Assignment Operator
What is a
copy-assignment
operator? When is this operator used? What does the synthesizedcopy-assignment
operator do? When is it synthesized?
The copy-assignment
operator is function named operator=
.This operator is used when assignment occurred.The synthesized copy-assignment
operator assigns each non-static member of the right-hand object to corresponding member of the left-hand object using the copy-assignment
operator for the type of that member.It is synthesized when the class does not define its own
What is a destructor? What does the synthesized destructor do? When is a destructor synthesized?
The destructor is a member function with the name of the class prefixed by a tilde(~
).As with the copy constructor and the copy-assignment operator, for some classes, the synthesized destructor is defined to disallow objects of the type from being destroyed. Otherwise, the synthesized destructor has an empty function body.
The compiler defines a synthesized destructor for any class that does not define its own destructor.Just as a constructor has an initialization part and a function body (§ 7.5.1, p. 288), a destructor has a function body and a destruction part. In a constructor, members are initialized before the function body is executed, and members are initialized in the same order as they appear in the class. In a destructor,the function body is executed first and then the members are destroyed. Members are destroyed in reverse orderfrom the order in which they were initialized
In a destructor, there is nothing akin to the constructor initializer list to control how members are destroyed; the destruction part is implicit. What happens when a member is destroyed depends on the type of the member.Members of class type are destroyed by running the member’s own destructor. The built-in types do not have destructors, so nothing is done to destroy members of built-in typenote: The destructor is not run when a reference or a pointer to an object goes out of scope.
{
// new scope
// p and p2 point to dynamically allocated objects
Sales_data *p = new Sales_data; // p is a built-in pointer
auto p2 = make_shared<Sales_data>(); // p2 is a shared_ptr
Sales_data item(*p); // copy constructor copies *p into item
vector<Sales_data> vec; // local object
vec.push_back(*p2); // copies the object to which p2 points
delete p; // destructor called on the object pointed to by p
}
// exit local scope; destructor called on item, p2, and vec
// destroying p2 decrements its use count; if the count goes to 0, the object is freed
// destroying vec destroys the elements in vec
#include <iostream>
#include <vector>
#include <initializer_list>
struct X {
X() { std::cout << "X()" << std::endl; } //1
X(const X&) { std::cout << "X(const X&)" << std::endl; } //2
X& operator=(const X&) //3
{
std::cout << "X& operator=(const X&)" << std::endl;
return *this;
}
~X() { std::cout << "~X()" << std::endl; } //4
};
void f(const X& rx, X x) // 2
{
std::vector<X> vec; //omit skip the constructor
vec.reserve(2); //omit skip the constructor
X y = x; //2
y=rx; //3
vec.push_back(rx); //2
vec.push_back(x); //2
}// destroy y, vec(twice), x //4 times
int main()
{
X* px = new X; //1
f(*px, *px);
delete px; //4
return 0;
}
The Rule of Three/Five
Classes That Need Destructors Need Copy and Assignment
Classes That Need Copy Need Assignment, and Vice Versa
Preventing Copies
The Destructor Should Not be a Deleted Member
reason:It is not possible to define an object or delete a pointer to a dynamically allocated object of a type with a deleted destructor
The Copy-Control Members May Be Synthesized as Deleted
1、The synthesized destructor is defined as deleted if the class has amember whose own destructor is deleted or is inaccessible (e.g., private).
2、The synthesized copy constructor is defined as deleted if the class has a member whose own copy constructor is deleted or inaccessible.It is also deleted if the class has a member with a deleted or inaccessible destructor.
3、The synthesized copy-assignment operator is defined as deleted if a member
has a deleted or inaccessible copy-assignment operator, or if the class has a
const or reference member.
4、The synthesized default constructoris defined as deleted if the class has a member with a deleted or inaccessible destructor;or has a reference member that does not have an in-class initializer (§ 2.6.1, p. 73);or has a const member whose type does not explicitly define a default constructor and that member does not have an in-class initializer
note:In essence, the copy-control members are synthesized as deleted when it is impossible to copy, assign, or destroy a member of the class
Swap
string *temp = v1.ps; // make a temporary copy of the pointer in v1.ps
v1.ps = v2.ps; // assign the pointer in v2.ps to v1.ps
v2.ps = temp; // assign the saved pointer in v1.ps to v2.ps
instead
void swap(HasPtr &lhs, HasPtr &rhs)
{
using std::swap;
swap(lhs.ps, rhs.ps); // swap the pointers, not the string data
swap(lhs.i, rhs.i); // swap the int members
}
swap Functions Should Call swap, Not std::swap
void swap(Foo &lhs, Foo &rhs)
{
using std::swap;
swap(lhs.h, rhs.h); // uses the HasPtr version of swap
// swap other members of type Foo
}
Using swap in Assignment Operators
Assignment operators that use copy and swap are automatically exception safe and correctly handle self-assignment
#include <string>
#include <iostream>
class HasPtr
{
public:
friend void swap(HasPtr&, HasPtr&);
friend bool operator<(const HasPtr &lhs, const HasPtr &rhs);
HasPtr(const std::string &s = std::string())
: ps(new std::string(s)), i(0)
{ }
HasPtr(const HasPtr &hp)
: ps(new std::string(*hp.ps)), i(hp.i)
{ }
HasPtr& operator=(HasPtr tmp)
{
this->swap(tmp);
return *this;
}
~HasPtr()
{
delete ps;
}
void swap(HasPtr &rhs)
{
using std::swap;
swap(ps, rhs.ps);
swap(i, rhs.i);
std::cout << "call swap(HasPtr &rhs)" << std::endl;
}
void show() const
{
std::cout << *ps << std::endl;
}
private:
std::string *ps;
int i;
};
void swap(HasPtr& lhs, HasPtr& rhs)
{
lhs.swap(rhs);
}
bool operator<(const HasPtr &lhs, const HasPtr &rhs)
{
return *lhs.ps < *rhs.ps;
}
Essentially, the specific avoiding memory allocation is the reason why it improve performance. As for the pointerlike version, no dynamic memory allocation anyway. Thus, a specific version for the valuelike will not improve the performance.
Swap
string *temp = v1.ps; // make a temporary copy of the pointer in v1.ps
v1.ps = v2.ps; // assign the pointer in v2.ps to v1.ps
v2.ps = temp; // assign the saved pointer in v1.ps to v2.ps
instead
void swap(HasPtr &lhs, HasPtr &rhs)
{
using std::swap;
swap(lhs.ps, rhs.ps); // swap the pointers, not the string data
swap(lhs.i, rhs.i); // swap the int members
}
swap Functions Should Call swap, Not std::swap
void swap(Foo &lhs, Foo &rhs)
{
using std::swap;
swap(lhs.h, rhs.h); // uses the HasPtr version of swap
// swap other members of type Foo
}
Using swap in Assignment Operators
Assignment operators that use copy and swap are automatically exception safe and correctly handle self-assignment
#include <string>
#include <iostream>
class HasPtr
{
public:
friend void swap(HasPtr&, HasPtr&);
friend bool operator<(const HasPtr &lhs, const HasPtr &rhs);
HasPtr(const std::string &s = std::string())
: ps(new std::string(s)), i(0)
{ }
HasPtr(const HasPtr &hp)
: ps(new std::string(*hp.ps)), i(hp.i)
{ }
HasPtr& operator=(HasPtr tmp)
{
this->swap(tmp);
return *this;
}
~HasPtr()
{
delete ps;
}
void swap(HasPtr &rhs)
{
using std::swap;
swap(ps, rhs.ps);
swap(i, rhs.i);
std::cout << "call swap(HasPtr &rhs)" << std::endl;
}
void show() const
{
std::cout << *ps << std::endl;
}
private:
std::string *ps;
int i;
};
void swap(HasPtr& lhs, HasPtr& rhs)
{
lhs.swap(rhs);
}
bool operator<(const HasPtr &lhs, const HasPtr &rhs)
{
return *lhs.ps < *rhs.ps;
}
Essentially, the specific avoiding memory allocation is the reason why it improve performance. As for the pointerlike version, no dynamic memory allocation anyway. Thus, a specific version for it will not improve the performance.
StrVec::StrVec(StrVec &&s) noexcept // move won't throw any
exceptions
// member initializers take over the resources in s
: elements(s.elements), first_free(s.first_free),
cap(s.cap)
{
// leave s in a state in which it is safe to run the destructor
s.elements = s.first_free = s.cap = nullptr;
}
allocate any resources. As a result, move operations ordinarily will not throw any
exceptions. When we write a move operation that cannot throw, we should inform the
library of that fact. As we’ll see, unless the library knows that our move constructor
won’t throw, it will do extra work to cater to the possibliity that moving an object of
our class type might throw.
the library interacts with objects of the types we write. We need to indicate that a
move operation doesn’t throw because of two interrelated facts:
StrVec &StrVec::operator=(StrVec &&rhs) noexcept
{
// direct test for self-assignment
if (this != &rhs) {
free(); // free existing elements
elements = rhs.elements; // take over resources from rhs
first_free = rhs.first_free;
cap = rhs.cap;
// leave rhs in a destructible state
rhs.elements = rhs.first_free = rhs.cap = nullptr;
}
return *this;
}
Warning
After a move operation, the “moved-from” object must remain a valid, destructible object but users may make no assumptions about its value
The compiler synthesizes the move constructor and move assignment only if
a class does not define any of its own copy-control members and only if all
the data members can be moved constructed and move assigned,
respectively.
// the compiler will synthesize the move operations for X and hasX
struct X {
int i; // built-in types can be moved
std::string s; // string defines its own move operations
};
struct hasX {
X mem; // X has synthesized move operations
};
X x, x2 = std::move(x); // uses the synthesized move constructor
hasX hx, hx2 = std::move(hx); // uses the synthesized move constructor
class HasPtr {
public:
HasPtr(const std::string &s = std::string()): ps(new std::string(s)), i(0) { }
// each HasPtr has its own copy of the string to which ps points
HasPtr(const HasPtr &p): ps(new std::string(*p.ps)), i(p.i) { }
/*
HasPtr& operator=(const HasPtr &rhs)
{
auto newp = new string(*rhs.ps); // copy the underlying string
delete ps; // free the old memory
ps = newp; // copy data from rhs into this object
i = rhs.i;
return *this; // return this object
}*/
~HasPtr() { delete ps; }
// added move constructor
HasPtr(HasPtr&& rhs) noexcept
: ps(rhs.ps),i(rhs.i)
{
rhs.ps = 0;
}
// assignment operator is both the move-and copy-assignment operator
HasPtr& operator=(HasPtr rhs)
{
swap(*this,rhs);
return *this;
}
private:
std::string *ps;
int i;
};
hp = hp2; // hp2 is an lvalue; copy constructor used to copy hp2
hp = std::move(hp2); // move constructor moves hp2
In the second assignment, we invoke std::move to bind an rvalue reference to hp2. In this case,both the copy constructor and the move constructor are viable.However, because the argument is an rvalue reference, it is an exact match for the move constructor. The move constructor copies the pointer from hp2. It does not allocate any memory.
All five copy-control members should be thought of as a unit: Ordinarily, if a class defines any of these operations, it usually should define them all. As we’ve seen, some classes must define the copy constructor, copy-assignment operator, and destructor to work correctly . Such classes typically have a resource that the copy members must copy. Ordinarily, copying a resource entails some amount of overhead. Classes that define the move constructor and move-assignment operator can avoid this overhead in those circumstances where a copy isn’t necessary.