C++中的拷贝,指的是我们在对对象进行拷贝,移动,赋值,销毁时,类的代码起到了什么作用,我们可以控制这些过程。
拷贝主要有两个操作:拷贝构造,拷贝赋值。
移动也是两个:移动构造,移动赋值。
最后我们还可以自定义析构对象时具体做什么事情【其实编译器已经帮助我们完成了通用的销毁等操作,但当我们有特殊需求比如某一个对象析构是不确定的,在析构时需要发生一些动作,我们可以定制这些动作】
一、 首先是拷贝构造函数:初始化分直接初始化和拷贝初始化,拷贝构造函数是指:第一个参数是本类型的引用(一般为常量引用,但是不是必须的),额外参数均有默认值。
class1(10,”ssds”);//直接初始化,调用与之匹配的函数
class1=”ssds”;//拷贝初始化,通过拷贝构造函数完成
注意一些explicit构造函数(explicit指定单参数构造函数无法执行隐式类型转换,多参数无需指定explicit,且不能执行隐式类型转换),比如vector<int>v1(10);//直接初始化vector<int> v1=10;//拷贝初始化,构造函数声明为explicit,故错误。
二、 拷贝赋值:就是通常使用的,class c1,c2(10);c1=c2;
注意如istream等类的对象是不允许拷贝赋值的,这时我们可以通过如下手段定义:
1) 拷贝构造函数是删除的(=delete)
2) 析构函数是private或删除的
3) 类有const或引用成员
三、 拷贝控制和资源管理:
这一部分主要目的是说明了如何编写行为像一个值或者像一个指针的类,我们通过构造函数,拷贝赋值函数的编写来实现这种特性。
1) 首先是行为像值得类,比如string类:
a) 拷贝构造函数需要有默认值形如:拷贝是通过动态分配内存完成的,两个拷贝构造函数分别拷贝string类和Mystring类,重载+,=运算符实现类似string的+和=。
classMystring {
public:
Mystring(conststring&s = string()) :ps(newstring(s)), i(0) {}
Mystring(Mystring &ms) :ps(newstring(*ms.ps)), i(ms.i) {}//以上两个拷贝构造函数均构造传入参数的string的副本
Mystring& operator=(constMystring &ms) = default;//定义相等操作
Mystring& operator+(constMystring &ms);
private:
string *ps;
int i;
};
Mystring &Mystring::operator+(constMystring &ms)
{
auto news = *ps+*ms.ps;
delete ps;
ps = &news;
i += ms.i;
return *this;
}
2) 行为像指针的类,
四、 交换操作:
1) swap函数:
对于内置类型,我们可以使用,std::swap()函数进行交换;
对于自定义类,我们使用类内定义的swap函数进行交换;且在不指定为std::swap时,如果有类内的swap,编译器自动使用类内swap。在编写时格式如下:
using std::swap;
swap(lhs.h,rhs.h);
这样,如果存在类内定义的swap能精确匹配,会使用类方法,如果没有,则使用std::方法。using声明是使编译器在找不到类内方法时不会报错,而是使用std::swap()
五、 动态内存
1) allocator类:
用来获得原始内存,通过consruct创建对象,通过destroy销毁对象,通过通过deallocate返还内存。
六、 对象的移动:
1) 左右值引用:&,&&。左值一般描述变量身份,右值描述一个值。不能将一个右值引用直接绑定到另一个右值引用变量上。一个右值引用变量是一个左值。int &&r1=70;int&&r2=r1;//r2的赋值错误,不能绑定到左值。
2) std::move()函数。1中已说,一个右值引用不能绑定到左值上,但是可以用move函数把左值转换为一个右值,从而右值引用可以帮i的那个到move(左值)上。所以int &&r2=std::move(rr1);正确。
3) 移动操作主要涉及构造和运算符,即(t1已构造好):classm t2(std::move(t1));classmt3=std::move(t2);//两者均调用移动构造函数。classm t4;t4=std::move(t3);//调用移动运算符
4) 移动构造函数的自动生成:在类成员均有移动构造函数时,编译器会为此类自动生成移动构造函数,比如类成员有几个int,int是内置类型,均有移动构造函数,因此会自动生成改类的移动构造函数,或者类成员有int,string,classt,如果classt未定义自己的移动构造函数,则不会生成类的移动构造函数。
但是实测结果是可以使用移动构造函数
classb1
{
int m;
std::string s1;
public:
b1()= default;
b1(inta, std::stringb) :m(a), s1(b) {}
b1(b1& bt) :m(bt.m), s1(bt.s1) {}
};
classb2
{
int m;
b1 bb;
public:
b2()= default;
b2(inta, b1b) :m(a), bb(b) {}
b2(b2& bt) :m(bt.m), bb(bt.bb) {}
};
int main()
{
b1 b11(1,"abcd");
b1 b12(2, "abcde");
b1 b13(3, "abcdef");
b1b14(std::move(b12));
b1 b15 =std::move(b14);
b2 b21(1, b11);
b2 b22(1, b13);
b2b23(std::move(b21));
b2 b24 = std::move(b23);
}