共计 3056 个字符,预计需要花费 8 分钟才能阅读完成。
1. 构造函数基础概念
构造函数是 C ++ 中用于初始化对象的特殊成员函数,它与类名相同且没有返回类型。当创建类对象时,构造函数会被自动调用,负责完成对象的初始化工作。

2. 构造函数调用方式
2.1 默认构造函数调用
默认构造函数是不需要任何参数的构造函数。它可以由编译器自动生成,也可以由程序员显式定义。
class MyClass {
public:
MyClass() { // 默认构造函数
std::cout << "Default constructor called" << std::endl;
}
};
int main() {
MyClass obj; // 隐式调用默认构造函数
MyClass obj2 = MyClass(); // 显式调用默认构造函数
return 0;
}
2.2 参数化构造函数调用
参数化构造函数接受一个或多个参数,用于初始化对象的状态。
class Point {
int x, y;
public:
Point(int a, int b) : x(a), y(b) { // 参数化构造函数
std::cout << "Parameterized constructor called" << std::endl;
}
};
int main() {Point p1(10, 20); // 直接调用
Point p2 = Point(30, 40); // 显式调用
return 0;
}
2.3 拷贝构造函数调用
拷贝构造函数用于用一个已存在的对象初始化一个新对象。
class Student {
std::string name;
public:
Student(const std::string &n) : name(n) {}
// 拷贝构造函数
Student(const Student &other) : name(other.name) {std::cout << "Copy constructor called" << std::endl;}
};
int main() {Student s1("Alice");
Student s2 = s1; // 调用拷贝构造函数
Student s3(s1); // 另一种调用方式
return 0;
}
2.4 移动构造函数调用
移动构造函数是 C ++11 引入的特性,用于高效转移资源所有权。
class ResourceHolder {
int *data;
public:
ResourceHolder(int size) : data(new int[size]) {}
// 移动构造函数
ResourceHolder(ResourceHolder &&other) noexcept : data(other.data) {
other.data = nullptr;
std::cout << "Move constructor called" << std::endl;
}
~ResourceHolder() { delete[] data; }
};
int main() {ResourceHolder rh1(100);
ResourceHolder rh2 = std::move(rh1); // 调用移动构造函数
return 0;
}
2.5 委托构造函数调用
C++11 允许构造函数调用同一类的其他构造函数,称为委托构造。
class Rectangle {
int width, height;
public:
Rectangle() : Rectangle(1, 1) {} // 委托给下面的构造函数
Rectangle(int w, int h) : width(w), height(h) {std::cout << "Rectangle constructed with" << width << "x" << height << std::endl;}
};
int main() {
Rectangle r1; // 调用委托构造函数
Rectangle r2(4, 5);
return 0;
}
3. 构造函数调用机制
3.1 内存管理角度
当创建一个对象时,编译器会执行以下步骤:
- 分配内存空间
- 调用构造函数初始化对象
- 构造函数执行期间,对象的虚函数表指针被设置
- 构造函数返回后,对象完全构造完成
3.2 调用方式比较
| 调用方式 | 优点 | 缺点 |
|---|---|---|
| 默认构造 | 简单直接 | 可能初始化不完整 |
| 参数化构造 | 可以精确控制初始化 | 需要提供所有参数 |
| 拷贝构造 | 保持对象状态一致 | 可能引发深层复制性能问题 |
| 移动构造 | 高效转移资源 | 需要确保源对象处于可移动状态 |
| 委托构造 | 减少代码重复 | 可能增加调用层次 |
4. 高级应用场景
4.1 继承体系中的构造顺序
class Base {
public:
Base() { std::cout << "Base constructor" << std::endl;}
};
class Derived : public Base {
public:
Derived() { std::cout << "Derived constructor" << std::endl;}
};
int main() {
Derived d; // 先调用 Base 构造函数,再调用 Derived 构造函数
return 0;
}
4.2 虚继承中的构造顺序
class A {public: A() {std::cout << "A" << std::endl;} };
class B : virtual public A {public: B() {std::cout << "B" << std::endl;} };
class C : virtual public A {public: C() {std::cout << "C" << std::endl;} };
class D : public B, public C {public: D() {std::cout << "D" << std::endl;} };
int main() {
D d; // 构造顺序:A -> B -> C -> D
return 0;
}
4.3 完美转发与构造函数
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
class Widget {
public:
Widget(int, double) {}};
int main() {auto w = make_unique<Widget>(42, 3.14); // 完美转发参数到构造函数
return 0;
}
5. 性能与安全问题
5.1 常见性能问题
- 不必要的拷贝构造调用
- 深层复制大型对象
- 多次重新分配资源
- 虚函数表设置开销
5.2 内存安全隐患
- 构造函数中抛出异常导致内存泄漏
- 未正确初始化指针成员
- 资源获取即初始化 (RAII) 使用不当
- 移动后未正确置空源对象
6. 生产环境最佳实践
- 遵循 RAII 原则:在构造函数中获取资源,在析构函数中释放
- 优先使用初始化列表:比构造函数体内赋值更高效
- 考虑异常安全:确保构造函数失败时不会泄漏资源
- 显式声明关键构造函数 :如
explicit避免隐式转换 - 合理使用 =default 和 =delete:明确表达设计意图
- 移动语义优化:对资源管理类实现移动构造函数
- 避免虚函数调用:构造函数中虚函数机制尚未完全建立
7. 思考题
- 在多线程环境中,如何确保构造函数的线程安全?
- 当使用 CRTP(奇异递归模板模式)时,构造函数的调用顺序与普通继承有什么不同?
- 如何设计一个只能通过工厂方法创建,而不能直接构造的类?
8. 总结
构造函数是 C ++ 对象生命周期管理的起点,理解各种构造函数调用方式及其背后的机制,对于编写高效、安全的 C ++ 代码至关重要。现代 C ++ 提供了丰富的构造函数特性,从传统的拷贝构造到移动语义,从委托构造到完美转发,合理运用这些特性可以显著提升代码质量和性能。在实际开发中,应当根据具体场景选择合适的构造函数实现方式,同时注意避免常见的陷阱和性能问题。
正文完
