「生活可以更简单, 欢迎来到我的开源世界」
  1. 编译器默认为一个类生成的函数
  2. 编译器生成拷贝赋值运算符的相关约束
  3. 总结
Item5-了解 C++ 默认添加和调用的函数
2021-01-01
」 「

Item 5: 了解 C++ 默默编写并调用哪些函数

编译器默认为一个类生成的函数

如果你自己不声明一个拷贝构造函数,一个拷贝赋值运算符和一个析构函数,编译器就会为这些东西声明一个它自己的版本。此外,如果你没有声明任何构造函数,编译器也会为你声明一个 default 构造函数。所有这些函数都被声明为 public 和 inline。作为结果,如果你写:

class Empty{};

就好像你写下了这样的代码:

class Empty {
public:
Empty() { ... } //@ default constructor
Empty(const Empty& rhs) { ... } //@ copy constructor
~Empty() { ... } //@ destructor — see below,for whether it's virtual
Empty& operator=(const Empty& rhs) { ... } //@ copy assignment operator
};

这些函数只有在它们被需要(被调用)的时候才会生成,程序中需要它们是很平常的事。下面的代码会促使每一个函数被编译器生成:

Empty e1;    //@ default constructor;
//@ destructor
Empty e2(e1); //@ copy constructor
e2 = e1; //@ copy assignment operator

生成的析构函数是非虚拟的,除非它所在的类是从一个基类继承而来,而基类自己声明了一个虚拟析构函数,这种情况下,函数的虚拟性来自基类。

对于拷贝构造函数和拷贝赋值运算符,编译器生成版本只是简单地从源对象拷贝每一个非静态数据成员到目标对象。

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); //@ calls copy constructor

nameValue 的类型是 string,标准 string 类型有一个拷贝构造函数,所以将通过以 no1.nameValue 作为参数调用 string 的拷贝构造函数初始化 no2.nameValue。而另一方面,NamedObject::objectValue 的类型是 int,而 int 是内建类型,所以将通过拷贝 no1.objectValue 的每一个二进制位初始化 no2.objectValue。

编译器生成拷贝赋值运算符的相关约束

编译器生成的拷贝赋值运算符行为基本上与拷贝构造函数一致,但一般只有在生成的代码合法而且有适当机会证明它有意义时,编译器才会默认生成。万一两个条件有一个不符合,编译器会拒绝为 class 生成 operator=

template<class T>
class NamedObject {
public:
NamedObject(std::string& name, const T& value);
//@ assume no operator= is declared

private:
std::string& nameValue; //@ this is now a reference
const T objectValue; //@ this is now const
};

std::string newDog("Persephone");
std::string oldDog("Satch");
NamedObject<int> p(newDog, 2);
NamedObject<int> s(oldDog, 36);
p = s;

赋值之前,p.nameValue 和 s.nameValue 是不同的 string 对象的引用。如果赋值成功,则会修改引用绑定的对象,但是 C++ 并不允许“让 reference 改指向不同对象”。面对这种情况,编译器不知道该怎么办,C++的响应是拒绝编译那一行的赋值操作。如果你打算在一个“内含 reference 成员”的 class 内支持赋值操作,必须自己定义 copy assignment 操作符。

面对“内含 const 成员“的 class,编译器的反应也一样,更改 const 成员是不合法的,所以编译器不知道如何在它自己生成的赋值函数内面对它们。

如果某个 base class 将 copy assignment 操作符声明为 private ,编译器将拒绝其 derived class 生成一个 copy assignment 操作符,毕竟编译器为派生类生成的拷贝赋值运算符想象中可以处理基类成员,但实际上它无法调用派生类无权调用的成员函数。

需要注意的是,含有引用成员和 const 成员对拷贝赋值运算符的约束对于拷贝构造函数是不适用的:

std::string newDog("Persephone");
NamedObject<int> p(newDog, 2);
NamedObject<int> p2(p); //@ 调用编译器默认生成的拷贝构造函数

总结

<⇧>