如果你不自己声明,编译器会替你声明(编译器版本的)拷贝构造函数、拷贝赋值运算符和析构函数。此外,如果你没有声明任何构造函数,编译器会为你声明一个默认构造函数。
class Empty{};
本质上和写成下面这样是一样的:
class Empty {public:Empty() { ... } // 默认构造函数Empty(const Empty& rhs) { ... } // 拷贝构造函数~Empty() { ... } // 析构函数Empty& operator=(const Empty& rhs) { ... } // 拷贝赋值运算符};
- 默认构造函数和析构函数主要为编译器提供了一个放置“幕后”代码的地方,例如调用基类和非静态数据成员的构造函数和析构函数。
- 拷贝构造函数和拷贝赋值运算符:编译器生成的版本只是简单地将源对象的每个非静态数据成员拷贝到目标对象。
只要我们自己定义的类中存在构造函数,编译器就不会默认为我们提供构造函数,如下:
template<typename T>class NamedObject {public:NamedObject(const char* name, const T& value);NamedObject(const std::string& name, const T& value);...private:std::string nameValue;T objectValue;};NamedObject<int> no1("Smallest Prime Number", 2);NamedObject<int> no2(no1); // 调用拷贝构造函数
如果我们的类中存在以下情况:存在引用或者常量的情况,编译器可能拒绝为你的类生成相应的代码。
我们可以看到报错了,这时候我们需要自己实现。
#include template<class T>class NamedObject {public:// 这个构造函数不再接受常量名称,因为nameValue现在是非常量字符串的引用。NamedObject(std::string& name, const T& value):nameValue(name), objectValue(value){}// 和以前一样,没有声明operator=void operator=(const NamedObject& item){this->nameValue = item.nameValue;}void printInfo();private:std::string& nameValue; // 现在这里是引用const T objectValue; // 现在这里是常量};template<class T>void NamedObject<T>::printInfo(){std::cout << "name:" << nameValue << "age:" << objectValue << std::endl;}int main(){std::string newDog("Persephone");std::string oldDog("Satch");NamedObject<int> p(newDog, 2); // Persephone, 2岁NamedObject<int> s(oldDog, 36); // Satch,36岁p.printInfo();p = s; // p中的数据成员应该如何处理" />.printInfo();}
最后一种需要注意的情况,如果基类的拷贝赋值运算符声明为private,它的派生类将无法生成拷贝赋值运算符,因为无法调用基类部分的函数处理基类部分。
编译器可以隐式地生成类的默认构造函数、拷贝构造函数、拷贝赋值运算符和析构函数。