《C++编程规范:101条规则、准则与最佳实践》——2.9 确保资源为对象所拥有。使用显式的RAII和智能指针

简介:

本节书摘来自异步社区出版社《C++编程规范:101条规则、准则与最佳实践》一书中的第2章,第2.9节,作者:【加】Herb Sutter , 【罗】Andrei,更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.9 确保资源为对象所拥有。使用显式的RAII和智能指针

摘要
利器在手,不要再徒手为之:C++的“资源获取即初始化”(Resource Acquisition Is Initialization,RAII)惯用法是正确处理资源的利器。RAII使编译器能够提供强大且自动的保证,这在其他语言中可是需要脆弱的手工编写的惯用法才能实现的。分配原始资源的时候,应该立即将其传递给属主对象。永远不要在一条语句中分配一个以上的资源。

讨论
C++语言所强制施行的构造函数/析构函数对称反映了资源获取/释放函数对比如fopen/fclose、lock/unlock和new/delete的本质的对称性。这使具有资源获取的构造函数和具有资源释放的析构函数的基于栈(或引用计数)的对象成为了自动化资源管理和清除的极佳工具。

这种自动化很容易实现、简洁、低成本而且天生防错。如果不予采用,就需要手工将调用正确配对,包括存在分支控制流和异常的情形,这可是很不容易而且需要注意力高度集中的任务。既然C++已经通过易用的RAII提供了如此直接的自动化,这种C语言式的仍然依赖于对资源解除分配的微观管理方式就是不可接受的了。

每当处理需要配对的获取/释放函数调用的资源时,都应该将资源封装在一个对象中,让对象为我们强制配对,并在其析构函数中执行资源释放。例如,我们无需直接调用一对非成员函数 OpenPort/ClosePort,而是可以考虑如下方法:

class Port {
public:
 Port( const string& destination ); // 调用OpenPort
 ~Port();                       // 调用ClosePort
 // ……通常无法复制端口,因此需要禁用复制和赋值……
};

void DoSomething() {
 Port port1( "server1:80" );
 // ……
}// 不会忘记关闭_port1_;它会在作用域结束时自动关闭

shared_ptr<Port> port2 = /*…*/;    // port2在最后一个引用它的
                               // shared_ptr离开作用域后关闭```
还可以使用实现了这种模式的软件库(参阅[Alexandrescu00c])。

在实现RAII时,要小心复制构造和赋值(见第49条),编译器生成的版本可能并不正确。如果复制没有意义,请通过将复制构造和赋值设为私有并且不做定义来明确禁用二者(见第53条)。否则,让复制构造函数复制资源或者引用计数所使用的次数,并让赋值操作符如法炮制,如果必要,同时还要确保它释放了最开始持有的资源。一个经典的疏漏是在新资源成功复制之前释放了老资源(见第71条)。

确保所有资源都为对象所有。最好用智能指针而不是原始指针来保存动态分配的资源。同样,应该在自己的语句中执行显式的资源分配(比如new),而且每次都应该马上将分配的资源赋予管理对象(比如shared_ptr),否则,就可能泄漏资源,因为函数参数的计算顺序是未定义的(见第31条)。例如:

void Fun( shared_ptr sp1, shared_ptr sp2 );
// ……
Fun( shared_ptr(new Widget), shared_ptr(new Widget) );``
这种代码是不安全的。C++标准给了编译器巨大的回旋余地,可以将构成函数两个参数的两个表达式重新排序。说得更具体一些,就是编译器可以交叉执行两个表达式:可能先执行两个对象的内存分配(通过调用operator new),然后再试图调用两个Widget构造函数。这恰恰为资源泄漏准备了温床,因为如果其中一个构造函数调用抛出异常的话,另一个对象的内存就永远也没有机会释放了!(详细情况请参阅 [Sutter02]。)

这种微妙的问题有一个简单的解决办法:遵循建议,绝对不要在一条语句中分配一个以上的资源,应该在自己的代码语句中执行显式的资源分配(比如new),而且每次都应该马上将分配的资源赋予管理对象(比如shared_ptr)。例如:

shared_ptr<Widget> sp1(new Widget), sp2(new Widget);
Fun( sp1, sp2 );```
另见第31条,了解使用这种风格的其他优点。

例外情况
智能指针有可能会被过度使用。如果被指向的对象只对有限的代码(比如纯粹在类的内部,诸如一个Tree类的内部节点导航指针)可见,那么原始指针就够用了。

参考文献
[Alexandrescu00c] ● [Cline99] §31.03-05 ● [Dewhurst03] §24, §67 ● [Meyers96] §9-10 ● [Milewski01] ● [Stroustrup00] §14.3-4, §25.7, §E.3, §E.6 ● [Sutter00] §16 ● [Sutter02] §20-21 ● [Vandevoorde03] §20.1.4
相关文章
|
23天前
|
缓存 安全 编译器
C++面试周刊(3):面试不慌,这样回答指针与引用,青铜秒变王者
《C++面试冲刺周刊》第三期聚焦指针与引用的区别,从青铜到王者级别面试回答解析,助你21天系统备战,直击高频考点,提升实战能力,轻松应对大厂C++面试。
71 10
C++面试周刊(3):面试不慌,这样回答指针与引用,青铜秒变王者
|
24天前
|
存储 C++
C++语言中指针变量int和取值操作ptr详细说明。
总结起来,在 C++ 中正确理解和运用 int 类型地址及其相关取值、设定等操纵至关重要且基础性强:定义 int 类型 pointer 需加星号;初始化 pointer 需配合 & 取址;读写 pointer 执向之处需配合 * 解引用操纵进行。
135 12
|
6月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
323 6
|
9月前
|
存储 程序员 C++
深入解析C++中的函数指针与`typedef`的妙用
本文深入解析了C++中的函数指针及其与`typedef`的结合使用。通过图示和代码示例,详细介绍了函数指针的基本概念、声明和使用方法,并展示了如何利用`typedef`简化复杂的函数指针声明,提升代码的可读性和可维护性。
252 1
|
10月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
531 4
|
11月前
|
存储 安全 编译器
在 C++中,引用和指针的区别
在C++中,引用和指针都是用于间接访问对象的工具,但它们有显著区别。引用是对象的别名,必须在定义时初始化且不可重新绑定;指针是一个变量,可以指向不同对象,也可为空。引用更安全,指针更灵活。
|
11月前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
154 2
|
11月前
|
存储 C++
c++的指针完整教程
本文提供了一个全面的C++指针教程,包括指针的声明与初始化、访问指针指向的值、指针运算、指针与函数的关系、动态内存分配,以及不同类型指针(如一级指针、二级指针、整型指针、字符指针、数组指针、函数指针、成员指针、void指针)的介绍,还提到了不同位数机器上指针大小的差异。
351 1
|
11月前
|
存储 C++ 索引
C++函数指针详解
【10月更文挑战第3天】本文介绍了C++中的函数指针概念、定义与应用。函数指针是一种指向函数的特殊指针,其类型取决于函数的返回值与参数类型。定义函数指针需指定返回类型和参数列表,如 `int (*funcPtr)(int, int);`。通过赋值函数名给指针,即可调用该函数,支持两种调用格式:`(*funcPtr)(参数)` 和 `funcPtr(参数)`。函数指针还可作为参数传递给其他函数,增强程序灵活性。此外,也可创建函数指针数组,存储多个函数指针。
225 6
|
11月前
|
存储 安全 编译器
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(一)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
157 1