Copy and Move
拷贝构造
默认拷贝
void func(Currency p){
cout << "x = " << p.dollars();
}
...
Currency bucks(100, 0);
func(bucks);
这个时候会发生隐式的拷贝和构造:Currency p(bucks);
。
这种特殊的构造,会调用拷贝构造函数:
class HowMany{
public:
HowMany(){}
~HowMany(){}
HowMany(const HowMany&);// 这个
private:
}
HowMany::HowMany(const HowMany& x){}
什么时候需要自己重新写拷贝构造:成员变量中有指针、非内存的资源(文件句柄,socket 等)。默认的拷贝构造是 member-wise copy,依次调用其成员的拷贝构造函数,指针只拷贝指向的地址而不拷贝资源。
编译器的优化细节:函数要返回本地对象的时候,编译器通过某种方法将其地址安排在调用它的函数旁边(前面),在函数结束后母函数仍然能直接访问此对象,从而避免了拷贝构造。
自定义拷贝构造
假设我们有个 Person
类,使用 char*
存储人名,每次构造时动态分配空间。
显然,在拷贝构造时我们需要复制一份新的人名,避免旧对象销毁之后,新对象无法访问人名。
class Person{
public:
Person(const char* s);
~Person();
Person(const Person& rhs);
void print();
// other func...
private:
char *name;
Person& operator=(const Person& rhs); // forbidden "="
// more info...
};
Person::Person(const char* s){
name = new char[::strlen(s) + 1];
::strcpy(name, s);
}
Person::~Person(){
delete[] name;
}
Person::Person(const Person& rhs){
name = new char[::strlen(rhs.name) + 1];
::strcpy(name, rhs.name);
}
如果不需要拷贝构造,可以将其声明为 private
,在外部调用时就会报错。这种情况下,不需要函数实现。
private:
Person(const Person& rhs);
参数与返回类型
基本类型:
void f(Student i);
void f(Student* p);
void f(Student& p);
Student f();
Student* f();
Student& f();
可能有的问题:
char* foo(){
char *p;
p = new char[10];
strcpy(p, "something");
return p;
}
void bar(){
char *p = foo();
delete[] p;// if the function is more complicated, maybe need or not
}
在工程中,非常难判断是否需要删除内存。所以,除非语义上需要,不要让对象的 new
和 delete
分离,不要让一个函数申请一块内存再把它传递出去。
移动拷贝构造
语法
含义:参数为右值引用的拷贝构造。
原型:
DynamicArray::DynamicArray(const DynamicArray&& rhs);
vector<int> v1{1, 2, 3, 4};
vector<int> v2 = v1; // v2 是 v1 的副本
vector<int> v3 = std::move(v1); // 调用移动构造函数
统一初始化 Uniform Initialization
这是 C++11 标准启用的新特性。
对于简单的类或者容器内部的类,我们可以不写构造函数,而是用花括号进行统一初始化。
如果类没有构造函数,参数应按照成员的声明顺序给出;如果有,参数应按照构造函数的参数顺序给出。
class Test{
int a, b;
};
Test t{0, 0};
Test *pt = new Test{1, 2};
int *a = new int[3]{1, 2, 0};
vector<string> vec = { "first", "second", "third"};
查看更多:Brace initialization for classes, structs, and unions | Microsoft Learn