C++类编译器自动生成函数的实现
在C++中,一个类有八个默认函数:
- 默认构造函数;
- 默认拷贝构造函数;
- 默认析构函数;
- 默认重载赋值运算符函数;
- 默认重载取址运算符函数;
- 默认重载取址运算符const函数;
- 默认移动构造函数(C++11);
- 默认重载移动赋值操作符函数(C++11)。
class MyClass { public: MyClass(); // 默认构造函数 MyClass(const MyClass &); // 默认拷贝构造函数
~MyClass(); // 默认析构函数
MyClass& operator =(const MyClass &); // 默认重载赋值运算符函数
MyClass* operator &(); // 默认重载取址运算符函数
MyClass const * operator &() const; // 默认重载取址运算符const函数
MyClass(MyClass &&); // 默认移动构造函数
MyClass& operator=(MyClass &&); // 默认重载移动赋值操作符函数
};
1.默认构造函数
C++ 默认构造函数是对类中的参数提供默认值的构造函数。如果用户没有定义构造函数,那么编译器会给类提供一个默认的构造函数,但是只要用户自定义了任意一个构造函数,那么编译器就不会提供默认的构造函数。
在C++11中可通过=default显式指定采用默认构造函数。
2.默认拷贝构造函数
当类中没有定义拷贝构造函数时,编译器会默认提供一个拷贝构造函数,进行成员变量之间的拷贝。
具体来说,编译器会直接使用"="进行值拷贝。一般类型中,"="的行为是值拷贝,而成员中有指针类型时,则会指复制指针,不复制指针指向的内容,产生浅拷贝。
进一步,当类中某些成员的赋值运算符函数"="被delete掉(如unique_ptr,std::atomic等,或其他自定义类型),无法调用时,编译器将无法提供默认拷贝构造函数。例子见后文。
3.默认析构函数
如果用户没有定义构造函数,编译器将为我们创建一个默认析构函数。
4.默认重载赋值运算符函数
当程序没有显式地提供一个以本类或本类的引用为参数的赋值运算符重载函数时,编译器会自动生成这样一个赋值运算符重载函数。
默认重载赋值运算符函数与默认拷贝构造函数的行为类似,编译器会直接使用"="进行值拷贝,当类中某些成员的赋值运算符函数"="被delete掉时,编译器将无法提供默认的重载赋值运算符函数
5.默认重载取址运算符函数;
如果没有显式定义,编译器会自动生成默认的重载取址运算符函数,函数内部直接return this,一般使用默认即可。
6.默认重载取址运算符const函数
与重载取址运算符函数类似,取地址后返回const类型
7.默认移动构造函数(C++11)
满足以下条件时,编译器将生成默认的移动构造函数:
1)没有用户声明的复制构造函数
2)没有用户声明的复制赋值运算符,
3)没有用户声明的移动赋值运算符,
4)没有用户声明的析构函数,和
5)移动构造函数不会被隐式定义为已删除.
当类中某些成员的赋值运算符函数"="被delete掉(如unique_ptr,std::atomic等,或其他自定义类型),无法调用时,编译器将无法提供默认移动构造函数
8.默认重载移动赋值操作符函数(C++11)
默认重载移动赋值操作符函数的行为与默认移动构造函数的行为类似,不再赘述
9.验证(环境VS2019 x86,x64)
9.1默认拷贝构造函数
定义如下类A,用于标识调用了拷贝构造函数和移动构造函数
class A { public: A() = default; A(const A& a) { cout << "A& called" << endl; } A(A&& a) { cout << "A&& called" << endl; } };
同理,定义类B
class B { public: B() = default; B(const B& a) { cout << "B& called" << endl; } B(B&& a){ cout << "B&& called" << endl; } };
定义类C,包含类A和类B
class C { public: A a; B b; string str; vector<int> vec; };
调用:
int main() { C q; q.str = "123456"; q.vec.resize(100); C w(q); }
输出如下:
将类C中添加atomic类型成员,
class C { public: A a; B b; string str; vector<int> vec; atomic<int> x; };
上述代码将无法编译,报错信息如下:
9.2默认移动构造函数
继续使用9.1中类的定义,将main函数中的定义改为
int main() { C q; q.str = "123456"; q.vec.resize(100); C w(std::move(q)); }
结果仍是
将类C中的atomic成员删除,编译通过,输出结果为:
观察q中的成员str和vec,都为空,说明默认的移动构造函数调用了成员的移动构造函数
将类B改为
class B { public: B() = default; B(const B& a) { cout << "B& called" << endl; } B(B&& a) = delete; };
调用结果为
观察q中的成员str和vec,str的值为123456,vec的size为100。说明只要一个成员未提供移动构造函数,则编译器生成的默认移动构造函数中,将对所有成员都调用拷贝构造函数,不调用移动构造函数,即使其他成员声明了该函数