共计 2284 个字符,预计需要花费 6 分钟才能阅读完成。
对象创建时的构造函数调用
在 C ++ 中,构造函数是对象诞生的 ” 出生证明 ”。不同类型的对象创建方式,会直接影响构造函数的调用时机:

-
栈对象 :当我们在函数内部声明局部变量时,对象会在栈上分配内存,并立即调用构造函数。
void func() {MyClass obj; // 这里会调用 MyClass 的构造函数} // 离开作用域时自动调用析构函数 -
堆对象 :使用 new 操作符创建的对象,会在堆上分配内存后立即调用构造函数。
MyClass* pObj = new MyClass(); // 分配内存后调用构造函数 -
临时对象 :通常出现在函数返回值或类型转换时,生命周期短暂。
MyClass createObj() {return MyClass(); // 创建临时对象 }
继承体系中的构造顺序
在继承关系中,构造函数的调用遵循严格的顺序规则:
- 基类构造函数(按继承顺序)
- 成员对象构造函数(按声明顺序)
- 派生类构造函数
class Base {
public:
Base() { cout << "Base constructor" << endl;}
};
class Member {
public:
Member() { cout << "Member constructor" << endl;}
};
class Derived : public Base {
Member m;
public:
Derived() { cout << "Derived constructor" << endl;}
};
// 输出顺序:// Base constructor
// Member constructor
// Derived constructor
典型代码示例分析
示例 1:包含成员对象的类构造
class Engine {
public:
Engine() { cout << "Engine started" << endl;}
};
class Car {
Engine engine; // 成员对象
public:
Car() { cout << "Car assembled" << endl;}
};
// 使用:Car myCar; // 先输出 "Engine started",再输出 "Car assembled"
示例 2:继承体系构造顺序
class Animal {
public:
Animal() { cout << "Animal born" << endl;}
};
class Dog : public Animal {
public:
Dog() { cout << "Dog barked" << endl;}
};
// 使用:Dog buddy; // 先输出 "Animal born",再输出 "Dog barked"
示例 3:移动构造函数优化
class Buffer {
char* data;
size_t size;
public:
// 移动构造函数
Buffer(Buffer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr; // 防止双重释放
other.size = 0;
cout << "Moved buffer" << endl;
}
// 普通构造函数
Buffer(size_t sz) : size(sz) {data = new char[size];
cout << "Allocated buffer" << endl;
}
~Buffer() { delete[] data; }
};
Buffer createBuffer() {Buffer temp(1024);
return temp; // 这里会调用移动构造函数
}
避坑指南
构造函数中的异常处理
构造函数中抛出异常时,对象会被视为 ” 未完全构造 ”,不会调用析构函数。但已构造完成的成员和基类会被正常销毁。
class Problematic {
int* ptr;
public:
Problematic() : ptr(new int(42)) {throw runtime_error("Oops"); // 内存泄漏!}
~Problematic() { delete ptr;}
};
// 解决方案:使用智能指针
class Safe {
unique_ptr<int> ptr;
public:
Safe() : ptr(make_unique<int>(42)) {throw runtime_error("No leak now");
}
};
避免对象切片
当派生类对象被赋值给基类对象时会发生切片,丢失派生类特有的数据:
class Base {/*...*/};
class Derived : public Base {/* 额外成员 */};
Derived d;
Base b = d; // 切片发生!// 解决方法:使用指针或引用
Base& ref = d; // 安全
Base* ptr = &d; // 安全
初始化列表 vs 赋值语句
初始化列表直接初始化成员,而赋值语句先默认初始化再赋值,存在性能差异:
// 低效方式
class Slow {
string name;
public:
Slow(const string& n) {name = n; // 先默认构造,再赋值}
};
// 高效方式
class Fast {
string name;
public:
Fast(const string& n) : name(n) {} // 直接构造};
思考与实践
-
如何设计一个禁止拷贝但允许移动的类?
(提示:删除拷贝构造函数和拷贝赋值运算符,但保留移动操作) -
在构造函数中调用虚函数会有什么结果?
(提示:此时虚函数机制尚未完全建立,调用的是当前类的版本) -
当类有 const 成员时,为什么必须在初始化列表中初始化?
(提示:const 成员一旦初始化后就不能再赋值)
构造函数是 C ++ 对象生命周期的起点,理解其调用时机和内部机制,能帮助我们写出更安全高效的代码。希望本文的分析和示例能为你解惑,也欢迎在实践中探索更多构造函数的奥秘。
正文完
