有那些网站做平面设计订单,网站托管 建设方案,网站建设列入无形资产管理吗,银川邮件处理中心在哪里C初学者指南-3.自定义类型(第一部分)-析构函数 文章目录 C初学者指南-3.自定义类型(第一部分)-析构函数特殊的成员函数用户定义的构造函数和析构函数RAII示例#xff1a;资源处理示例#xff1a;RAII记录零规则 特殊的成员函数
T::T()默认构造函数当创建新的 T 对象时运行。…C初学者指南-3.自定义类型(第一部分)-析构函数 文章目录 C初学者指南-3.自定义类型(第一部分)-析构函数特殊的成员函数用户定义的构造函数和析构函数RAII示例资源处理示例RAII记录零规则 特殊的成员函数
T::T()默认构造函数当创建新的 T 对象时运行。T::T(param…)特殊构造函数创建带参数的新 T 对象时运行T::~T()析构函数当现有的 T 对象被销毁时运行
编译器会在我们没有自己定义的情况下生成一个默认构造函数和一个析构函数。 在后面的章节中我们将了解到四个特殊的成员可以用来控制类型的复制和移动行为。
copy constructor拷贝构造函数 T::T(T const)copy assignment operator拷贝赋值操作符函数 T T::operator (T const)move constructor移动构造函数 T::T(T )move assignment operator 移动赋值操作符函数 T T::operator (T )
它们通常也是由编译器自动生成的在许多/大多数情况下不需要用户自定义。
用户定义的构造函数和析构函数
class Point { … };
class Test {std::vectorPoint w_;std::vectorint 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::cout “destructor\n”;x的数据成员被销毁了 i_ 被销毁了基本类型没有析构函数v_ 被销毁 → 执行析构函数 ~vector() vector在其缓冲区中销毁整数元素基本类型→没有析构函数释放堆上的缓冲区内存v_的剩余数据成员已被销毁 w_ 被销毁 → 执行析构函数 ~vector() vector在其缓冲区中销毁Point元素每个~Point()析构函数都会被执行释放堆上的缓冲区内存w_的剩余数据成员被销毁
RAII
“资源获取即初始化”
对象构建获取资源对象销毁释放资源
示例std::vector
每个向量对象都拥有一个独立的堆上缓冲区在那里存储着实际内容。该缓冲区是根据需要分配的并且在向量对象被销毁时被释放。
所有权 如果一个对象负责资源的生命周期(初始化/创建、终结/销毁)我们就说它是资源(内存、文件句柄、连接、线程、锁……)的所有者。
提醒C 使用值语义 变量指向对象本身而不仅仅是引用/指针。 这是几乎所有编程语言中基本类型int、double等的默认行为也是C中用户自定义类型的默认行为
深拷贝生成一个新的、独立的对象对象成员的值被复制深层赋值使目标的值等于源对象的值深层所有权成员变量指向与包含对象具有相同生命周期的对象基于值的比较如果它们的数值相等/较小则变量进行相等/小于/… 的比较。
由于成员的生命周期与其包含的对象绑定在一起所以不需要垃圾回收器。
示例资源处理
常见情况 我们需要使用一个外部的 © 库它具有自己的资源管理。这些资源可以是内存还可以是设备、网络连接、已打开的文件等。 在这样的库中资源通常是通过初始化和清理函数来处理的比如 lib_init() 和 lib_finalize() 用户需要调用这些函数。 问题资源泄漏 通常在程序庞大且控制流复杂时经常会忘记调用最终清理函数。这可能导致设备卡住内存未被释放等问题。 解决方案RAII 包装器
在构造函数中调用初始化函数在析构函数中调用清理函数额外优势包装类还可以用来存储上下文信息如连接详情设备ID等这些只在初始化和结束之间有效这样的包装器大多数情况下应该是不可复制的因为它处理着独特的资源在后面的章节中会有更详细的解释
#include gpulib.hclass GPUContext {int gpuid_;
public:explicitGPUContext (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;// 在这里处理事情…} // 自动清理释放…
}示例RAII记录
Device的构造函数获得一个指向UsageLog对象的指针UsageLog 可以用来记录 Device 对象生命周期中的操作如果Device不再存在析构函数会通知UsageLogUsageLog 还可以统计活跃设备的数量等等
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:explicitDevice (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 风格的资源管理或基于生命周期的跟踪否则请避免编写特殊成员函数。 大多数情况下编译器生成的默认构造函数和析构函数已经足够了。
初始化并不总是需要编写构造函数。 大多数数据成员可以使用成员初始化器进行初始化。
不要为类型添加空析构函数 用户自定义析构函数的存在会阻止许多优化并严重影响性能
你几乎不需要写析构函数。 在C11之前使用自定义类并进行显式手动内存管理是非常常见的。然而在现代C中内存管理策略大多数情况下也应该封装在专用类容器智能指针分配器等中。
附上原文地址 如果文章对您有用请随手点个赞谢谢^_^