1、拷贝控制操作包括拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符和析构函数。
2、 拷贝构造函数:如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,此构造函数是拷贝构造函数。
public:
Foo(); //默认构造函数
Foo(const Foo&); //拷贝构造函数
};
拷贝构造函数的第一个参数是必须是自身类类型的引用类型,如果不是引用会无限递归。
3、合成拷贝构造函数:我们没有为类定义拷贝构造函数,编译器会帮我们定义一个。编译器从给定对象中依次将每个非static成员拷贝到正在创建的对象中。
4、拷贝初始化
拷贝初始化不仅在=定义变量时发生,还会在以下几种情况:
将一个对象作为实参传递给一个非引用类型的形参;
从一个返回类型为非引用类型的函数返回一个对象;
用花括号列表初始化一个数组中的元素或一个聚合类中的成员。
5、拷贝赋值运算符
Sales_data trans,accum;
trans=accum; //使用Sales_data的拷贝赋值运算符
和拷贝构造函数一样,如果类未定义拷贝赋值运算符,编译器会合成一个。
赋值构造函数必须注意它的函数原型,参数通常是引用类型,返回值通常也是引用类型,否则在传参和返回的时候都会再次调用一次拷贝构造函数。
6、析构函数
析构函数执行的与构造函数执行的顺序相反,析构函数释放对象所使用的资源,并销毁对象的非static数据成员。
析构函数不接受参数,不能被重载,对于一个类只有唯一一个析构函数。
隐式销毁一个内置指针类型的成员不会delete它所指向的对象。
在以下几种情况下会调用析构函数:
变量在离开其作用域的时候;
当一个对象被销毁时,其成员被销毁;
容器被销毁时;
当对指向它的指针应用delete运算符时被销毁;
对于临时对象,当创建它的完整表达式结束时被销毁。
#include<iostream>
#include<string>
using namespace std;
class Str{
public:
Str() = default;
Str(string s) :str(s),pstr(new string()){ cout << "构造函数" << endl; }
Str(const Str& s){
str = s.str;
pstr = new string(*s.pstr);
cout << "拷贝构造函数" << endl;
}
Str& operator=(const Str& s){
str = s.str;
pstr = new string(*s.pstr);
cout << "拷贝赋值运算符" << endl;
return *this;
}
~Str(){
delete pstr;
cout << "析构函数" << endl;
}
private:
string str;
string * pstr;
};
Str func(Str s){
return s;
}
int main(){
Str str1("aa"); //直接初始化
Str str2 = str1; //拷贝初始化
Str str3(str1); //拷贝初始化
Str str4;
str4 = str1; //赋值初始化
func(str1);//见拷贝初始化,调用2次拷贝构造函数和2次析构函数
system("pause");
return 0;
}
运行结果:
7、三/五法则
需要析构函数的类也需要拷贝和赋值操作。
需要拷贝操作的类也需要赋值操作,反之亦然。
8、 使用=default
我们可以通过将拷贝控制成员定义为=default来显示地让编译器来为我们生成默认版本。
9、 阻止拷贝:加上=delete
新标准下,我们可以通过将拷贝构造函数和赋值运算符定义为删除的函数来阻止拷贝和赋值。=delete必须出现在函数第一次声明的时候。析构函数不能是删除的成员。
删除一个类的析构函数或者类的某个成员的类型删除了析构函数,我们都不能定义该类的变量或临时对象。但可以动态分配这种类型,不能释放。
struct NoDtor{
NoDtor() = default; //使用合成默认构造函数
~NoDtor() = delete; //我们不能销毁NoDtor类型的对象
};
//NoDtor nd;//错误:NoDtor的析构函数时删除的
NoDtor *p = new NoDtor(); //正确:但不能delete p
//delete p; //错误:NoDtor的析构函数是删除的
10、行为像值的类:对于类资源的管理,每个对象都有一份自己的拷贝
#include <iostream>
#include <string>
using namespace std;
class HasPtr{
friend ostream& print(std::ostream &os, const HasPtr&);
public:
HasPtr(const string& s = string()) :ps(new std::string(s)), i(0) { cout << "构造函数" << endl; }
HasPtr(const HasPtr& p) :ps(new string(*p.ps)), i(p.i){ cout << "拷贝构造函数" << endl; }
HasPtr& operator=(const HasPtr&);
~HasPtr(){
delete ps;
cout << "析构函数" << endl;
}
private:
string* ps;
int i;
};
HasPtr& HasPtr::operator = (const HasPtr &p){
auto newp = new string(*p.ps);//考给临时变量,万一=左值是自己就释放了。
delete ps;
ps = newp;
i = p.i;
cout << "拷贝赋值运算符" << endl;
return *this;
}
ostream& print(std::ostream &os, const HasPtr& p){
std::cout << "string:" << *p.ps << " int:" << p.i << std::endl;
return os;
}
int main(){
HasPtr p1;
HasPtr p2("hehe");
print(cout, p1);
print(cout, p2);
p1 = p2;
print(cout, p1);
system("pause");
return 0;
}
11、行为像指针的类
#include <iostream>
#include <string>
using namespace std;
class HasPtr{
friend ostream& print(std::ostream &os, const HasPtr&);
public:
HasPtr(const string& s = string()) :ps(new std::string(s)), i(0), use(new size_t(1)){ cout << "构造函数" << endl; }
HasPtr(const HasPtr& p) :ps(p.ps), i(p.i), use(p.use){
++*use;
cout << "拷贝构造函数" << endl;
}
HasPtr& operator=(const HasPtr&);
~HasPtr();
private:
string* ps;
int i;
size_t* use;
};
HasPtr& HasPtr::operator = (const HasPtr &rhs){
++*rhs.use; //递增右侧运算对象的引用计数
if (--*use == 0){ //递减本对象的引用计数
delete ps;
delete use;
}
ps = rhs.ps; //将数据从rhs拷贝到本对象
i = rhs.i;
use = rhs.use;
return *this;
}
HasPtr::~HasPtr(){
if (--*use == 0){ //如果引用计数变为0
delete ps; //释放string内存
delete use; //释放计时器内存
}
cout << "析构函数" << endl;
}
ostream& print(std::ostream &os, const HasPtr& p){
std::cout << "string:" << *p.ps << " int:" << p.i << " use:" << *p.use << std::endl;
return os;
}
int main(){
HasPtr p1;
HasPtr p2("hehe");
print(cout, p1);
print(cout, p2);
p1 = p2;
print(cout, p1);
system("pause");
return 0;
}