共计 2355 个字符,预计需要花费 6 分钟才能阅读完成。
构造函数基础概念
构造函数是 C ++ 中用于初始化对象的特殊成员函数,它在对象创建时自动调用。理解构造函数的调用方式对于编写高效、安全的 C ++ 代码至关重要。

- 默认构造函数 :当类没有显式定义任何构造函数时,编译器会自动生成一个无参的默认构造函数。
- 参数化构造函数 :可以接受一个或多个参数,用于定制对象的初始化方式。
- 拷贝构造函数 :用于创建一个对象的副本,通常在对象作为参数传递或从函数返回时调用。
- 移动构造函数 (C++11 引入):用于将资源从一个临时对象转移到新对象,提高性能。
构造函数的调用方式
显式调用
显式调用是指在代码中直接调用构造函数的方式。
- 最常见的是在创建对象时直接调用:
MyClass obj(10); // 调用参数化构造函数 - 使用 new 运算符时:
MyClass* pObj = new MyClass(10); - 在成员初始化列表中调用(用于派生类构造函数):
Derived::Derived() : Base(10) {}
隐式调用
隐式调用是指编译器自动调用构造函数的情况,开发者不需要显式写出调用语句。
- 默认构造函数的隐式调用:
MyClass obj; // 调用默认构造函数 - 类型转换时的隐式调用:
void func(MyClass obj); func(10); // 隐式调用 MyClass(int) 构造函数 - 返回值优化(RVO)时的隐式调用。
委托构造函数(C++11)
委托构造函数允许一个构造函数调用同一个类中的另一个构造函数,简化代码并避免重复。
class MyClass {
public:
MyClass() : MyClass(0) {} // 委托给下面的构造函数
MyClass(int value) : m_value(value) {}
private:
int m_value;
};
构造函数调用的内存管理机制
理解构造函数调用的底层机制有助于编写更高效的代码。
- 对象创建时的内存分配 :
- 栈上对象:编译器自动管理内存,构造函数调用后直接初始化
- 堆上对象:先分配内存,再调用构造函数
- 构造函数的执行顺序 :
- 基类构造函数
- 成员变量的构造函数(按声明顺序)
- 派生类构造函数
- 异常安全 :构造函数中抛出异常时,已构造的成员和基类会被自动销毁
典型场景代码示例
示例 1:继承体系中的构造顺序
class Base {
public:
Base() { cout << "Base constructor" << endl;}
};
class Derived : public Base {
public:
Derived() { cout << "Derived constructor" << endl;}
};
int main() {
Derived d; // 输出顺序:Base constructor -> Derived constructor
return 0;
}
示例 2:移动构造函数的应用
class ResourceHolder {
public:
ResourceHolder(size_t size) : size(size), data(new int[size]) {}
// 移动构造函数
ResourceHolder(ResourceHolder&& other) noexcept
: size(other.size), data(other.data) {
other.data = nullptr; // 确保原对象析构时不释放资源
other.size = 0;
}
~ResourceHolder() { delete[] data; }
private:
size_t size;
int* data;
};
ResourceHolder createResource() {ResourceHolder temp(100);
return temp; // 触发移动构造函数
}
示例 3:拷贝构造与移动构造的对比
class String {
public:
// 拷贝构造函数(深拷贝)String(const String& other) {
size = other.size;
data = new char[size];
memcpy(data, other.data, size);
}
// 移动构造函数(资源转移)String(String&& other) noexcept {
size = other.size;
data = other.data;
other.data = nullptr;
other.size = 0;
}
private:
size_t size;
char* data;
};
性能问题与内存安全
- 不必要的拷贝 :
- 隐式拷贝构造可能导致性能问题
- 解决方案:使用移动语义或传递引用
- 内存泄漏风险 :
- 构造函数中分配资源但析构函数未释放
- 构造函数抛出异常导致资源泄漏
- 循环依赖 :
- 两个类的构造函数相互调用会导致无限递归
生产环境最佳实践
- 遵循规则三 / 五 / 零 :
- 如果需要自定义析构函数、拷贝构造函数或拷贝赋值运算符,通常需要同时定义这三个(规则三)
- C++11 后扩展为规则五(加上移动构造函数和移动赋值运算符)
- 最佳实践是遵循规则零:让编译器生成默认实现
- 优先使用移动语义 :
- 对于管理资源的类,实现移动构造函数和移动赋值运算符
- 异常安全 :
- 构造函数中避免可能抛出异常的操作
- 或使用 RAII 技术确保资源安全
- 避免虚构造函数 :
- C++ 不支持虚构造函数,这是语言设计决定
- 可以使用工厂模式替代
思考题
- 在什么情况下,编译器会隐式生成默认构造函数?什么情况下不会?
- 如何设计一个类,使得它只能通过特定方式(如工厂方法)创建对象?
- 在多继承情况下,构造函数的调用顺序是怎样的?如何控制这个顺序?
总结
掌握 C ++ 构造函数的各种调用方式是编写高质量 C ++ 代码的基础。从简单的默认构造到复杂的移动语义,每种调用方式都有其特定的使用场景和优化空间。在实际开发中,我们应该根据具体需求选择合适的构造函数实现方式,同时注意内存安全和性能问题。现代 C ++ 提供了更多强大的工具(如委托构造、移动语义)来简化代码并提高效率,善用这些特性可以让我们的代码更加健壮和高效。
正文完
