灵光一现的问题和常见错误1
拷贝构造函数显式写,编译器还会自动生成默认构造函数吗,还有什么函数会出现这种问题
在C++中,当类显式定义某些特殊成员函数时,编译器可能不再自动生成其他相关函数。以下是详细分析:
I. 显式定义拷贝构造函数对默认构造函数的影响
规则
-
默认行为:如果一个类没有显式声明任何构造函数,编译器会生成一个默认的无参构造函数。
-
例外情况:如果类显式声明了任何构造函数(包括拷贝构造函数),编译器不再自动生成默认构造函数。
示例
class MyClass {
public:// 显式定义拷贝构造函数MyClass(const MyClass& other) { /* ... */ }
};int main() {MyClass obj1; // 错误:默认构造函数被抑制MyClass obj2(obj1); // 正确:调用拷贝构造函数return 0;
}
解决方法
显式定义默认构造函数:
class MyClass {
public:MyClass() = default; // 显式要求编译器生成默认构造函数MyClass(const MyClass& other) { /* ... */ }
};
II. 其他受影响的特殊成员函数
除了默认构造函数,以下函数的自动生成规则也会受到显式定义其他函数的影响:
显式定义的函数 | 编译器不再自动生成的函数 |
---|---|
拷贝构造函数 | 默认构造函数、移动构造函数、移动赋值运算符 |
拷贝赋值运算符 | 移动构造函数、移动赋值运算符 |
移动构造函数 | 拷贝构造函数、拷贝赋值运算符 |
移动赋值运算符 | 拷贝构造函数、拷贝赋值运算符 |
析构函数 | 移动构造函数、移动赋值运算符 |
III. 具体场景分析
(1) 显式定义拷贝构造函数
-
抑制的自动生成函数:
-
默认构造函数(需显式定义)
-
移动构造函数(需显式定义)
-
移动赋值运算符(需显式定义)
-
(2) 显式定义析构函数
-
抑制的自动生成函数:
-
移动构造函数(需显式定义)
-
移动赋值运算符(需显式定义)
-
(3) 显式定义移动构造函数
-
抑制的自动生成函数:
-
拷贝构造函数(需显式定义)
-
拷贝赋值运算符(需显式定义)
-
IV. C++11 的改进:= default
和 = delete
为了更精细地控制编译器生成的特殊成员函数,C++11引入了显式语法:
(1) 强制生成默认函数
class MyClass {
public:MyClass() = default; // 强制生成默认构造函数MyClass(const MyClass&) = default; // 强制生成拷贝构造函数MyClass(MyClass&&) = default; // 强制生成移动构造函数
};
(2) 禁用特定函数
class NonCopyable {
public:NonCopyable() = default;NonCopyable(const NonCopyable&) = delete; // 禁止拷贝NonCopyable& operator=(const NonCopyable&) = delete;
};
V. 总结:编译器自动生成函数的条件
特殊成员函数 | 自动生成条件 |
---|---|
默认构造函数 | 没有显式声明任何构造函数 |
拷贝构造函数 | 没有显式声明拷贝构造函数、移动构造函数、移动赋值运算符或析构函数 |
拷贝赋值运算符 | 没有显式声明拷贝赋值运算符、移动构造函数、移动赋值运算符或析构函数 |
移动构造函数 | 没有显式声明拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符或析构函数 |
移动赋值运算符 | 没有显式声明拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符或析构函数 |
析构函数 | 始终自动生成(除非显式声明) |
VI. 最佳实践
-
显式定义或禁用关键函数:如果需要自定义拷贝、移动或析构逻辑,显式定义相关函数。
-
优先使用
= default
:保留编译器生成的默认行为,避免手动实现冗余代码。 -
注意移动语义的兼容性:在定义拷贝操作时,考虑是否需要显式定义移动操作。
通过合理控制特殊成员函数的生成,可以避免潜在的错误,并确保类的行为符合预期。