C++ primer 第十三章复习
13.1 拷贝,赋值与销毁
类 有五种特殊成员函数控制对象拷贝,移动,赋值和销毁
拷贝构造函数
拷贝赋值运算符
移动构造函数
移动赋值运算符
析构函数
上述操作称为拷贝控制操作,若一个类没有定义这些函数,编译器会自动生成缺失的函数
拷贝构造函数
拷贝构造函数通常不应该是 explicit ( explicit 不可以隐式初始化对象)
class Foo{ public: Foo(){} //构造函数 Foo(const Foo&){} //拷贝构造函数 };
在C++中若构造函数只有一个参数, 那么在编译时会有一个缺省的转换:将该构造函数对应数据类型数据转换为该类对象,例如
CxString string2 = 10
//转换为
CxString string2(10);
或
CxString temp(10);
CxString string2 = temp;
#include <iostream>using namespace std; class Test{ public: Test(){} //拷贝构造函数 Test(const Test &t){ cout << "in copy" << endl; data = t.data; } Test& Test::operator=(const Test &t){ std::cout << "in =" << std::endl; return *this; } private: int data; }; int main(){ Test t1; Test t2(t1);//由于是显式调用拷贝构造函数,所以编译过 Test t3; t3 = t2; Test t4 = t2;//由于是隐式调用拷贝构造函数,所以编译不过 return 0; } //没有初始化对象无法调用 = 操作符函数(),所以如果要走 = 操作符函数,那么需要 Test t4; t4 = t2; 两步.故 Test t4 = t2 会被编译器优化为 Test t4(t2)
合成拷贝构造函数
涉及到其它类的拷贝,拷贝构造函数内部会调用其它类的拷贝构造
class Sales_data{ public: Sales_data(const Sales_data&); private: std::string bookNo; int units_sold =0; }; Sales_data::Sales_data(const Sales_data& orig): bookNo(orig.bookNo), //调用string的拷贝构造函数 units_sold(orig.units_sold){}
拷贝初始化
拷贝构造函数用来初始化非引用类型参数,所以自己的参数必须是引用(如果不是引用,又会调用参数的拷贝构造)
std::string dots(10, '.'); //构造函数初始化 std::string str1(dots); //显式拷贝构造函数初始化 std::string str2 = dots; //隐式拷贝构造函数初始化 std::string str3 ="9-99-999"; //构造函数初始化 std::string str4 = std::string(100, '9');//显式构造函数 + 隐式拷贝构造函数初始化
拷贝初始化的限制
explicit 单参构造函数,无法使用 = 成员值进行拷贝构造,作为函数参数同理
explicit vector(size_type n); //vec的构造函数 vector (const vector& x); //vec的拷贝构造函数 std::vector<int> v1(10); //正确,构造函数初始化 std::vector<int> v2 =10; //错误,构造函数是 explicit 的 void f(std::vector<int>); f(10); //错误,构造函数是 explicit 的 f(std::vector<int>(10)); //正确, std::vector<int> tmp = std::vector<int>(10);
编译绕过拷贝构造函数
std::string str3 ="9-99-999"; //改写为 std::string str3("9-99-999"); //直接走构造函数初始化,而不会走隐式构造函数 + 隐式拷贝构造函数初始化
拷贝赋值运算符
拷贝赋值运算符接受一个调用类相同类型的参数
class Foo{ public: Foo(){} Foo(const Foo&){} //拷贝赋值运算符接受一个调用类相同类型的参数 Foo& operator==(const Foo&){} }; int main(){ Foo f2; Foo f1; f1 = f2; }
合成拷贝赋值运算符
涉及到其它类的拷贝运算符,拷贝运算符会调用其它类的拷贝运算符
class Sales_data{ public: Sales_data(const Sales_data&); Sales_data& operator=(const Sales_data&); private: std::string bookNo; int units_sold =0; }; Sales_data& Sales_data::operator=(const Sales_data&){ bookNo = orig.bookNo; units_sold = orig.units_sold; //调用 string 的拷贝运算符 return *this; }
析构函数
不接受参数,不允许重载
构造函数初始化对象的非 static 数据成员 (先初始化成员再构造函数 父 -> 子)
析构函数释放对象成员,并销毁非 static 数据成员(先调用析构函数,再释放成员 子 -> 父)
先调用 A 析构,再销毁 A 数据成员 ,最后销毁父类 B,和创建刚好相反
class Foo{ public: ~Foo(){} };
1、成员初始化顺序:只与类成员的声明顺序有关
2、初始化列表与构造函数体内初始化的区别:(内置数据类型除外)
在成员初始化列表中初始化,和在构造函数体内赋值,内置数据类型,复合类型(指针,引用)性能和结果完全一样,但用户定义类型(类类型)结果相同,但性能上存在差别。因类类型的数据成员对象在进入函数体前已构造完成,在成员初始化列表处进行构造对象工作,若在构造函数体内赋值相当于先进行构造函数再调用拷贝运算符,而成员初始化列表只调用拷贝构造函数即可
合成析构函数
在类的析构函数结束后,会自动调用成员的析构函数
class Foo{ public: //成员会自动销毁,不需要额外动作 ~Foo(){} private: std::string data; };
需要析构函数的类也需要拷贝和赋值操作
析构释放指针的类,需要拷贝函数和赋值操作;需要拷贝操作的类,也需要赋值操作,反之亦然
对一个类的每个对象分配一个唯一的 ID :需要自定义拷贝构造函数和赋值运算符,但不需要自定义析构函数
class HashPtr{ public: //构造函数 HashPtr(const std::string& s = std::string()) : data(new std::string(s)), num(0){} //编译器生成的拷贝函数 HashPtr(const HashPtr& hashPtr){ (*this).data = hashPtr.data; this->num = hashPtr.num; } //编译器生成的拷贝操作符 const HashPtr& operator=(const HashPtr& hashPtr){ (*this).data = hashPtr.data; this->num = hashPtr.num; return *this; } //析构函数 ~HashPtr(){ delete data; } private: std::string* data; int num; }; HashPtr test(HashPtr hashPtr){ //调用编译器生成的拷贝构造 HashPtr hashPtr(p1) HashPtr ret = hashPtr; //调用编译器生成的拷贝操作符 return ret; // hashPtr 对象和 ret 将被销毁 } int main(){ HashPtr p1("some values"); test(p1); //调用结束后, p1.data 指向的内存被释放了 HashPtr p2(p1); //现在 p1,p2 的 data 变成了野指针 system("pause"); return 0; }
显式缺省函数
(=default) , default只能用于6个特殊成员函数,但delete可用于任何成员函数
=default 可以显式的要求编译器生成合成的版本
class Sales_data{ public: Sales_data() = default; Sales_data(const Sales_data&) = default; Sales_data& operator=(const Sales_data&); ~Sales_data() = default; private: std::string m_data; }; // = default 生成的赋值运算符函数返回的对象类型是 Sales_data& 非 const Sales_data& Sales_data::operator=(const Sales_data&) = default;
阻止拷贝
例,IOStream 类阻止拷贝,以免对个对象写入或读取相同的 IO 缓存
struct NoCopy { NoCopy() = default; //通知编译器,不希望定义这些成员函数 NoCopy(const NoCopy&) = delete; //阻止拷贝 NoCopy& operator=(const NoCopy&) = delete; ~NoCopy() = default; }; struct NoDtor { NoDtor() = default;//默认构造 ~NoDtor() = delete;//阻止析构 };
对于析构已删除的类,不能定义该类的对象或释放该类指针
struct NoDtor { NoDtor() = default;//默认构造 ~NoDtor() = delete;//阻止析构 }; int main(){ //NoDtor nd; //报错,编译检测,因为对象不能正常销毁 NoDtor* p = new NoDtor(); delete p; //报错 system("pause"); return 0; }
本质上,当不能 拷贝,赋值,删除 类成员时,类的合成拷贝函数已被定义为删除的
C++11 1前,阻止拷贝是通过 private 实现的,但是友元和成员函数仍可以拷贝
struct PrivateCopy { private: PrivateCopy(const PrivateCopy&); PrivateCopy& operator=(const PrivateCopy&); public: //默认的合成构造函数,可使用类对象无法拷贝 PrivateCopy() = default; ~PrivateCopy() = delete; }
单例模式
实现 = delete 实现单例
template<typename T> class Singleton { public: static T& GetInstance() { static T instance; return instance; } Singleton(T&&) = delete; Singleton(const T&) = delete; void operator= (const T&) = delete; protected: Singleton() = default; virtual ~Singleton() =