1 在c++中,每个类都有自己的构造函数,只有有些类拥有默认的构造函数,由编译器默认构造,叫做默认构造函数。除此之外,可以在类中显示定义构造函数。
例如:
class A{
public:
A(int a1):a(a1){
cout<<"construction A"<<endl;
}
~A(){
cout<<"destruction A"<<endl;
}
private:
int a;
};
2 除此之外,需要在类中对私有成员变量进行内存分配的类,除了需要普通的构造函数,还需要拷贝构造函数,拷贝赋值函数,析构函数这三大函数。一般这三大函数将会同时出现在同一个类中,如果你实现了其中的一个函数,那么也将实现另外两个函数。
For example:
class Array1{
public:
Array1(int n=8):_size(n),a(new int[n]){
cout<<"construction Array1 default"<<endl;
}
Array1(vector<int>& v){
_size=v.size();
a=new int[_size];
for(int i=0;i<_size;i++)
a[i]=v[i];
cout<<"construction Array1 vector"<<endl;
}
Array1(const Array1& array1):_size(array1._size),a(new int[_size]){
for(int i=0;i<_size;i++)
a[i]=array1.a[i];
cout<<"construction Array1 copy"<<endl;
}
Array1& operator=(const Array1& array1){
cout<<"construction Array1 assignment"<<endl;
_size=array1._size;
a=new int[_size];
for(int i=0;i<_size;i++){
a[i]=array1.a[i];
}
return *this;
}
~Array1(){
cout<<"Array1 class destruction"<<endl;
delete a;
}
private:
int _size;
int *a;
};
int main(){
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
Array1 base(v);
Array1 base1(base);
Array1 base2;
base2=base1;
}
3 在一些情况下,可能不需要拷贝构造和拷贝赋值函数,所以我们需要采用某些机制阻止拷贝构造和拷贝赋值。在正常情况下,可以不定义这些函数,但是默认情况下编译器将会为它生成合适的函数版本。
在c++11的标准中,我们可以通过将拷贝构造函数和拷贝赋值函数运算符定义为删除函数来阻止拷贝。删除函数是这样一种函数:我们虽然声明了它们,但不能以任何方式使用它们。在函数的参数列表后面加上=delete指出我们希望将它定义为删除的。
class A{
public:
A(int a1):a(a1){
cout<<"construction A"<<endl;
}
A(const A& a1)=delete;
A& operator=(const A& a1)=delete;
~A(){
cout<<"destruction A"<<endl;
}
private:
int a;
};
4 在c++11中,除了有以上函数,为了减少内存的分配,提高运行效率,提出了移动构造函数和移动赋值函数。移动构造函数主要是为了防止内存分配造成的效率问题。比如,在拷贝构造函数中,需要在类中重新开辟一块内存空间,但是在移动构造中,我们却不需要开辟内存空间,而是可以将对象指向源内存空间,消除了内存分配的时间,不消耗内存。
介绍这些之前,先说一下 右值引用的概念。
之前的c+++中引用概念主要为左值引用。左值引用主要是指被引用的变量可以长期存在,而不是一个临时的变量,所以左值引用主要是指变量。
而c++11中提出了一个新的引用叫右值引用。他可以绑定到临时变量,主要包括右值表达式,字符串常量等。
int x=4;
int &&y=x+4;//正确
int &&y=x;//错误 不能帮定左值
int &z=x+4;//错误 不能绑定临时变量
const int &z1=x+4//正确 因为临时变量不能修改
理解了右值引用后,移动构造函数就很容易理解了。
右值引用是用来支持转移语义的。转移语义可以将资源 ( 堆,系统对象等 ) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高 C++ 应用程序的性能。临时对象的维护 ( 创建和销毁 ) 对性能有严重影响。
/**
移动构造函数 参数为右值引用,不能为const
**/
Array1(Array1&& myArray){
cout<<"construction Array1 move"<<endl;
_size=myArray._size;
a=myArray.a;
myArray.a=nullptr;
myArray._size=0;
}
/**
移动赋值函数 参数为右值引用
**/
Array1& operator=(Array1&& myArray){
cout<<"construction Array1 move assignment"<<endl;
if(this!=&myArray){
_size=myArray._size;
a=myArray.a;
myArray._size=0;
myArray.a=nullptr;
}
return *this;
}
和拷贝构造函数类似,有几点需要注意:
1. 参数(右值)的符号必须是右值引用符号,即“&&”。
2. 参数(右值)不可以是常量,因为我们需要修改右值。
3. 参数(右值)的资源链接和标记必须修改。否则,右值的析构函数就会释放资源。转移到新对象的资源也就无效了。
移动构造函数的参数值必须是右值引用的,否则将会出现error.因为定义了移动构造函数后,拷贝构造函数将被编译器默认为delete删除函数
所以实参值必须是右值引用的。
5 标准库函数 std::move
既然编译器只对右值引用才能调用转移构造函数和转移赋值函数,而所有命名对象都只能是左值引用,如果已知一个命名对象不再被使用而想对它调用转移构造函数和转移赋值函数,也就是把一个左值引用当做右值引用来使用,怎么做呢?标准库提供了函数 std::move,这个函数以非常简单的方式将左值引用转换为右值引用。
示例程序:
void ProcessValue(int& i) {
std::cout << "LValue processed: " << i << std::endl;
}
void ProcessValue(int&& i) {
std::cout << "RValue processed: " << i << std::endl;
}
int main() {
int a = 0;
ProcessValue(a);
ProcessValue(std::move(a));
}
结果为:
LValue processed: 0
RValue processed: 0
C++11 中定义的 T&& 的推导规则为:
右值实参为右值引用,左值实参仍然为左值引用。
一句话,就是参数的属性不变。这样也就完美的实现了参数的完整传递。
右值引用,表面上看只是增加了一个引用符号,但它对 C++ 软件设计和类库的设计有非常大的影响。它既能简化代码,又能提高程序运行效率。每一个 C++ 软件设计师和程序员都应该理解并能够应用它。我们在设计类的时候如果有动态申请的资源,也应该设计转移构造函数和转移拷贝函数。在设计类库时,还应该考虑 std::move 的使用场景并积极使用它。