1️⃣ 问题引入
- 当定义一个
empty class
会发生什么?
- 编译器会为这个
empty class
自动声明:
copy
构造函数
copy assignment
操作符
析构函数
- 同理,如果没有定义任何构造函数,编译器也会声明一个
default
默认构造函数。
2️⃣ 案例
等价于:
1 2 3 4 5 6 7
| class Empty { public: Empty() { ... } Empty(const Empty& rhs) { ... } Empty& operator=(const Empty& rhs) { ... } ~Empty() { ... } };
|
3️⃣ 产生时机
- 只有这些函数被调用的时候,它们才会被编译器创建出来。
1 2 3
| Empty e1; Empty e2(e1); e2 = e1;
|
4️⃣ 对于拷贝的理解
- 浅拷贝:
- 对于
copy
构造函数、copy assignment
操作符,编译器创建的版本只是将来源对象的每一个非静态成员变量拷贝到目标对象。
案例:
1 2 3 4 5 6 7 8 9
| 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
没有声明 copy
构造函数,也没有声明 copy assignment
操作符。
- 如果发生如下代码,会怎么样?
1 2
| NamedObject<int> no1("Smallest Prime Number", 2); NamedObject<int> no2(no1);
|
解析:
- 编译器生成的
copy
构造函数将以 no1.nameValue
和 no1.objectValue
为初值,设定 no2
对应的两个成员。
nameValue
是 std::string
类型,会调用 string
的 copy
构造函数。
objectValue
是 int
类型(T = int),为内置类型,因此会直接拷贝 bits。
5️⃣ 对于 copy assignment 的理解
- 一般情况下,
copy assignment
操作符和 copy
构造函数相似。
- 但有时,编译器会拒绝为某些类生成
copy assignment
操作符。
案例:
1 2 3 4 5 6 7 8
| template<class T> class NamedObject { public: NamedObject(std::string& name, const T& value); private: std::string& nameValue; const T objectValue; };
|
如果执行如下代码:
1 2 3 4 5 6 7
| 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
对象。
- 是否能让
p.nameValue
指向 s.nameValue
?
- 不行,因为 C++ 不允许让
reference
改指向不同的对象。
编译器的应对方案:
- 如果 class 包含
reference
成员,想要支持赋值操作,必须自己定义 copy assignment
操作符。
- 类中如果包含
const
成员,也一样。
- 如果某个基类将
copy assignment
操作符声明为 private
,编译器也不会为派生类自动生成 copy assignment
操作符。
💡 总结
了解编译器在背后默默生成的函数有助于我们编写更稳定、可控的 C++ 类。
尤其当类中包含 reference
、const
或继承关系时,更要注意手动定义复制相关操作。