什么是Copy Control
在C++中,我们可以对类自定义拷贝构造器(copy constructor), 移动构造器(move constructor), 拷贝赋值运算符(copy-assignment operator),移动赋值运算符(move-assignment operator), 析构器(destructor)等方法。我们将这些方法统称为Copy Control
拷贝构造器(The Copy Constructor)
C++的每一个类都有一个拷贝构造器用于构造对象,其参数为同类型另一个对象的引用,这个参数几乎都是const的
class Foo {
public:
Foo(); // default constructor
Foo(const Foo&); // copy constructor
// ...
};
合成拷贝构造器(The Synthesized Copy Constructor)
如果我们不自己定义一个Copy Constructor,那么编译器会自动生成一个合成的(Synthesized) Copy Constructor,与默认构造器不同的是,即使我们定义了其他构造器,编译器还是会自动生成Copy Constructor。
合成的拷贝构造器会递归调用成员函数的合成构造器,但是它无法直接拷贝数组。
eg. 合成的拷贝构造器与下面代码展示的拷贝构造器相同
class Sales_data {
public:
// other members and constructors as before
// declaration equivalent to the synthesized copy constructor
Sales_data(const Sales_data&);
private:
std::string bookNo; int units_sold = 0; double revenue = 0.0;
};
// equivalent to the copy constructor that would be synthesized for Sales_data
Sales_data::Sales_data(const Sales_data &orig):
bookNo(orig.bookNo), // uses the string copy constructor
units_sold(orig.units_sold), // copies orig.units_sold
revenue(orig.revenue) // copies orig.revenue
{ } // empty body
拷贝初始化(Copy Initialization)
C++中变量的初始化分为拷贝初始化(Copy Initialization)和直接初始化(Direct Initialzation), 显式的使用构造器进行的初始化为直接初始化,使用=运算符进行的初始化为拷贝初始化。
eg.
string dots(10, '.'); // direct initialization
string s(dots); // direct initialization
string s2 = dots; // copy initialization
string null_book = "9-999-99999-9"; // copy initialization
string nines = string(100, '9'); // copy initialization
使用拷贝初始化时一般会直接调用拷贝构造器(*如果类拥有移动构造器,则在拷贝初始化时会使用移动构造器)
此外,拷贝初始化还会在以下场景中被使用
在function中传入非引用对象(nonreference object type)作为参数
在function中将非引用对象作为参数返回
大括号初始化数组中的元素或是聚合类的成员
*有些类还会在类分配内存时使用拷贝初始化
拷贝初始化的限制(Constraints on Copy Initialization)
在C++中,我们无法隐式的使用类创造的显式构造器
vector<int> v1(10); // ok: direct initialization
vector<int> v2 = 10; // error: constructor that takes a size is explicit
void f(vector<int>); // f's parameter is copy initialized
f(10); // error: can't use an explicit constructor to copy an argument
f(vector<int>(10)); // ok: directly construct a temporary vector from an int
因此在例子中,v2无法成功初始化
*在进行拷贝初始化时,编译器可以选择使用直接构造对象从而跳过拷贝或移动构造器
eg.
string null_book = "9-999-99999-9"; // copy initialization
// this sentence can be translated to sentence below within the compiler
string null_book("9-999-99999-9"); // compiler omits the copy
然而,即使编译器选择跳过拷贝或移动构造器,此拷贝或移动构造器还是必须存在并可以访问(eg. not private)
拷贝赋值运算符(The Copy-Assignment Operator)
在C++中,正如类控制着对象是如何被创建的,它同时也控制着对象是如何被赋值的。
Sales_data trans, accum;
trans = accum; // uses the Sales_data copy-assignment operator
同样的,如果类没有定义一个赋值运算符,编译器会自动生成一个合成的运算符
重载运算符(Overloaded Operators)
在C++中,我们可以重载一个类中的运算符,例如operator+, operator==, operator=等等
eg. 重载拷贝运算符
class Foo {
public:
Foo& operator=(const Foo&); // assignment operator
// ...
};
合成的拷贝赋值运算符(The Synthesized Copy-Assignment Operator)
与Copy Constructor类似,合成的拷贝赋值会将递归调用成员变量的拷贝赋值函数,最终将一个类对象的引用返回给左边的变量
eg.
// equivalent to the synthesized copy-assignment operator
Sales_data&
Sales_data::operator=(const Sales_data &rhs)
{
bookNo = rhs.bookNo; // calls the string::operator=
units_sold = rhs.units_sold; // uses the built-in int assignment
revenue = rhs.revenue; // uses the built-in double assignment
return *this; // return a reference to this object
}
析构器(The Destructor)
不同于Java等语言拥有自己的垃圾回收机制,C++需要使用者自己释放创造的对象中使用的内存。在每一个C++类中,都有一个析构函数(Destructor),其命名为~<objectname>
*不同于传统指针(ordinary pointer),C++中的智能指针(smart pointer)会在销毁阶段自动清理占用的内存空间
析构器的调用
C++对象会在以下情况下被销毁
变量会在超出运行范围时被销毁
一个对象成员对象会在它被销毁时销毁
容器中的对象(无论是array或是库容器)会在容器被销毁时销毁
动态分配的指针会在调用delete运算符时被销毁
临时对象会在超出表达式范围时被销毁
由此可见对象的销毁一般是自动的,因此大部分时间我们无需担心对象什么时候销毁,只需要注意销毁时如何释放内存即可
合成的析构器(The Synthesized Destructor)
同理,若不自定义析构函数,C++会自动生成一个合成的析构函数,合成的析构函数不对成员进行任何资源释放
class Sales_data {
public:
// no work to do other than destroying the members, which happens automatically
~Sales_data() { }
// other members as before
};
The Rule of Three/Five
对于类的拷贝构造器,拷贝赋值运算符,析构器,我们定义了一些规则以便开发者快速理解自己什么时候需要它们。
需要析构器的类需要拷贝构造器和拷贝赋值运算符
一个类是否需要析构器一般较为明显
eg. 如果一个类中有指针类型成员,它肯定需要析构器对分配的内存进行释放,那么如果这个类没有相应的拷贝构造器,那么进行拷贝过后指针类型成员会被直接复制,因此会有多个对象的指针指向同一块内存,由于这些对象都会进行销毁,delete <pointer> 将会被调用多次,释放已经释放的内存,会使程序报错,并且这个错误是未定义的。
需要拷贝构造器的类需要拷贝赋值运算符,反之同理
比较好理解就不解释了
Using = default
我们可以使用=default显式的告诉编译器对类使用合成的Copy Control成员
class Sales_data {
public:
// copy control; use defaults
Sales_data() = default; Sales_data(const Sales_data&) = default;
Sales_data& operator=(const Sales_data &); ~Sales_data() = default;
// other members as before
};
Sales_data& Sales_data::operator=(const Sales_data&) =
default;
合成的function是作为inline function存在的,如果我们不想让这个合成的成员为一个inline function,我们就可以把它设置为=default
避免拷贝的方法(Prevent Copies)
有时我们可能不想让一个类的对象被复制,我们有两种方法可以避免类的复制
定义方法=delete
我们可以显式的定义一个方法=delete,通过定义copy control function=delete,我们就可以避免这个类的复制
eg.
struct NoDtor {
NoDtor() = default; // use the synthesized default constructor
~NoDtor() = delete; // we can't destroy objects of type NoDtor
};
NoDtor nd; // error: NoDtor destructor is deleted
NoDtor *p = new NoDtor(); // ok: but we can't delete p
delete p; // error: NoDtor destructor is deleted
*由于一个类一定会在销毁时调用析构函数,因此我们不能定义类的析构函数=delete
使合成的Copy Control 为deleted
在一些情况下,编译器会自动合成copy control member as deleted
例如将一个函数设为private,这样这个函数在类之外就无法被调用,因此就会被编译器设置为deleted
eg. UE的Noncopyable类型,将拷贝构造器和拷贝复制运算符设置为了private
/**
* utility template for a class that should not be copyable.
* Derive from this class to make your class non-copyable
*/
class FNoncopyable
{
protected:
// ensure the class cannot be constructed directly
FNoncopyable() {}
// the class should not be used polymorphically
~FNoncopyable() {}
private:
FNoncopyable(const FNoncopyable&);
FNoncopyable& operator=(const FNoncopyable&);
};
*在C++ Primer中,作者推荐使用=delete而不是private来实现noncopyable
