构造函数调用规则

内容简介

构造函数决定对象如何被创建。默认情况下,编译器会为类生成一些特殊成员函数,但一旦用户自己定义了某些构造函数,默认生成规则就会变化。

默认提供的函数

如果类没有自定义特殊成员函数,编译器通常会提供:

  • 默认构造函数。
  • 默认析构函数。
  • 默认拷贝构造函数。
  • 默认拷贝赋值运算符。

现代 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 成员减少手写特殊成员函数。

学习路径