如果一个C++类没有声明自己的构造函数、拷贝构造函数、拷贝赋值运算符和析构函数的话,那么编译器就会为我们生成一个。所有这些由编译器为我们生成的函数都是public
且inline
的。这些函数只有当被调用到的时候,才会被创建出来。
但是这些函数都做了什么呢?主要是处理一些程序幕后的工作,即:编译器向这些函数中安插了额外的代码来处理一些常规事务,例如:调用父类的构造函数和析构函数、初始化虚函数表指针等。
至于拷贝构造函数和拷贝赋值运算符,编译器只是简单地将对象中的成员变量进行浅拷贝
。
例如下面的这个类:
template<typename T>
class NamedObject
{
public:
NamedObject(const char *name, const T &value);
private:
std::string _name;
T _objectValue;
};
//...
int main()
{
NamedObject<int> namedObject;
return 0;
}
在这个类中,由于类NamedObject
没有定义拷贝构造函数和拷贝赋值运算符,因此编译器将生成它们:分别针对对象的两个成员变量:_name
和_objectValue
进行浅拷贝。
其中,_name
是一个std::string
对象,它存在一个拷贝构造函数,因此编译器将直接使用它;此时参数类型T
被实例化为了int
。因此,编译器也将直接拷贝这个整型成员变量的每个字节。
但是,编译器也可能会拒绝为类生成拷贝构造函数和拷贝辅助运算符,这完全取决于生成出来的代码是否可用。例如:
template<typename T>
class NamedObject
{
public:
NamedObject(std::string &name, const T &value);
private:
std::string &_name;
const T _objectValue;
};
//...
int main()
{
NamedObject<int> dog("dog", 10);
NamedObject<int> cat("cat", 12);
dog = cat; //编译错误。
return 0;
}
此时,编译器将不会再为我们生成拷贝构造函数和拷贝赋值运算符,因为编译器认定默认的操作存在一定的风险:
- 如果修改了引用的对象,那么对应的原对象以及其他通过指针或者引用指向该对象的逻辑都可能发生变化(对于指针则无妨,编译器对引用添加了额外的限制)。
- 被
const
修饰的对象是具有二进制常量属性的,编译器无法使用默认的方法修改const对象
。
此时,如果想让程序正常运行,那么就必须自己提供拷贝构造函数和拷贝赋值运算符。
【注意】
- C++编译器会为我们合成一些与类生命周期相关的函数,但是同时也会检查这些代码的合法性;对于那些生成后不合法或者有风险的代码,编译器将强迫我们手动实现。