构造函数调用规则
内容简介
构造函数决定对象如何被创建。默认情况下,编译器会为类生成一些特殊成员函数,但一旦用户自己定义了某些构造函数,默认生成规则就会变化。
默认提供的函数
如果类没有自定义特殊成员函数,编译器通常会提供:
- 默认构造函数。
- 默认析构函数。
- 默认拷贝构造函数。
- 默认拷贝赋值运算符。
现代 C++ 还涉及移动构造和移动赋值,见 新对象特性。
用户定义有参构造函数
如果用户定义了有参构造函数,编译器不再自动提供默认无参构造函数。
class Person {
public:
explicit Person(int age) : age_(age) {}
private:
int age_ = 0;
};
Person p1(18); // 正确
// Person p2; // 错误:没有默认构造函数如果仍然需要无参构造,可以显式写出来:
class Person {
public:
Person() = default;
explicit Person(int age) : age_(age) {}
private:
int age_ = 0;
};默认拷贝构造
如果没有自定义拷贝构造,编译器提供的拷贝构造会逐个成员拷贝。
class Person {
public:
int age = 0;
};
Person p1;
p1.age = 18;
Person p2(p1);
std::cout << p2.age << '\n';对于普通值成员,这通常没问题;对于裸指针资源,就可能导致浅拷贝问题。
用户定义拷贝构造函数
如果用户定义拷贝构造函数,需要更认真考虑其他构造函数是否还需要显式提供。
class Person {
public:
Person(const Person& other) : age_(other.age_) {}
private:
int age_ = 0;
};这类类如果还需要默认构造或有参构造,应显式声明。
最佳代码实践
- 需要默认构造时,明确写
= default。 - 不允许拷贝时,明确写
= delete。 - 资源管理类要遵循三五法则或零法则。
- 普通业务对象优先使用标准库成员,让编译器生成默认特殊成员函数。
错误用法
class File {
public:
explicit File(const char* path);
~File();
private:
FILE* fp_;
};问题:这个类持有资源,却没有明确禁止拷贝或实现深拷贝,默认拷贝可能导致重复关闭文件。
注意事项
- “编译器会帮你生成函数”不等于“语义一定正确”。
- 有资源所有权时,默认拷贝往往是危险的。
- 现代 C++ 推荐用 RAII 成员减少手写特殊成员函数。
学习路径
- 上一节:cpp对象特性
- 当前阶段:cppOOP
- 下一节:cpp拷贝构造函数调用时机