赞
踩
T::T() | 默认构造函数 | 当创建新的 T 对象时运行。 |
T::T(param…) | 特殊构造函数 | 创建带参数的新 T 对象时运行 |
T::~T() | 析构函数 | 当现有的 T 对象被销毁时运行 |
编译器会在我们没有自己定义的情况下生成一个默认构造函数和一个析构函数。
在后面的章节中,我们将了解到四个特殊的成员,可以用来控制类型的复制和移动行为。
它们通常也是由编译器自动生成的,在许多/大多数情况下不需要用户自定义。
class Point { … };
class Test {
std::vector<Point> w_;
std::vector<int> v_;
int i_ = 0;
public:
Test() {
std::cout << "constructor\n";
}
~Test() {
std::cout << "destructor\n";
}
// more member functions …
};
if (…) {
…
Test x; // prints 'constructor'
…
} // prints 'destructor'
销毁时执行顺序
在析构函数体运行完毕后,所有数据成员的析构函数将按照声明的相反顺序执行。这是自动发生的,不能更改(至少不容易改 - 毕竟这是C++,几乎有可以绕过任何事情的方法)。
x 超出作用域范围→执行 ~Test():
“资源获取即初始化”
示例:std::vector
所有权
如果一个对象负责资源的生命周期(初始化/创建、终结/销毁),我们就说它是资源(内存、文件句柄、连接、线程、锁……)的所有者。
提醒:C++ 使用值语义
= 变量指向对象本身,而不仅仅是引用/指针。
这是几乎所有编程语言中基本类型(int、double等)的默认行为,也是C++中用户自定义类型的默认行为:
由于成员的生命周期与其包含的对象绑定在一起,所以不需要垃圾回收器。
常见情况
我们需要使用一个外部的 © 库,它具有自己的资源管理。这些资源可以是内存,还可以是设备、网络连接、已打开的文件等。
在这样的库中,资源通常是通过初始化和清理函数来处理的,比如 lib_init() 和 lib_finalize() ,用户需要调用这些函数。
问题:资源泄漏
通常在程序庞大且控制流复杂时,经常会忘记调用最终清理函数。这可能导致设备卡住,内存未被释放等问题。
解决方案:RAII 包装器
#include <gpulib.h> class GPUContext { int gpuid_; public: explicit GPUContext (int gpuid = 0): gpuid_{gpuid} { gpulib_init(gpuid_); } ~GPUContext () { gpulib_finalize(gpuid_); } [[nodiscard]] int gpu_id () const noexcept { return gpuid_; } // make non-copyable: GPUContext (GPUContext const&) = delete; GPUContext& operator = (GPUContext const&) = delete; }; int main () { … if (…) { // 创建和初始化上下文 GPUContext gpu; // 在这里处理事情 … } // 自动清理释放! … }
class File { … }; class DeviceID { … }; class UsageLog { public: explicit UsageLog (File const&); … void armed (DeviceID); void disarmed (DeviceID); void fired (DeviceID); }; class Device { DeviceID id_; UsageLog* log_; … public: explicit Device (DeviceId id, UsageLog* log = nullptr): id_{id}, log_{log}, … { if (log_) log_->armed(id_); } ~Device () { if (log_) log_->disarmed(id_); } void fire () { … if (log_) log_->fired(id_); } … }; int main () { File file {"log.txt"} UsageLog log {file}; … Device d1 {DeviceID{1}, &log}; d1.fire(); { Device d2 {DeviceID{2}, &log}; d2.fire(); } d1.fire(); }
log.txt
device 1 armed
device 1 fired
device 2 armed
device 2 fired
device 2 disarmed
device 1 fired
device 1 disarmed
= 尽量不要编写特殊成员函数
除非你需要进行 RAII 风格的资源管理或基于生命周期的跟踪,否则请避免编写特殊成员函数。
大多数情况下,编译器生成的默认构造函数和析构函数已经足够了。
初始化并不总是需要编写构造函数。
大多数数据成员可以使用成员初始化器进行初始化。
不要为类型添加空析构函数!
用户自定义析构函数的存在会阻止许多优化,并严重影响性能!
你几乎不需要写析构函数。
在C++11之前,使用自定义类并进行显式手动内存管理是非常常见的。然而,在现代C++中,内存管理策略大多数情况下(也应该)封装在专用类(容器,智能指针,分配器等)中。
附上原文地址
如果文章对您有用,请随手点个赞,谢谢!^_^
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。