《Effective C++》第3章 资源管理(2)-读书笔记

章节回顾:

《Effective C++》第1章 让自己习惯C++-读书笔记

《Effective C++》第2章 构造/析构/赋值运算(1)-读书笔记

《Effective C++》第2章 构造/析构/赋值运算(2)-读书笔记

《Effective C++》第3章 资源管理(1)-读书笔记

《Effective C++》第3章 资源管理(2)-读书笔记

《Effective C++》第8章 定制new和delete-读书笔记



条款15:在资源管理类中提供对原始资源的访问

许多API直接指涉资源,所以除非你永远不用它们,否则都会绕过资源管理对象直接访问原始资源。假设使用tr1::shared_ptr管理对象。

std::tr1::shared_ptr<Investment> pInv(createInvestment());

函数daysHeld的声明是这样的:

int daysHeld(const Investment *pi);

下面这种调用方式,肯定是错误的:

int days = daysHeld(pInv);        //错误

因为函数需要的是指针,你传递是一个tr1::shared_ptr<Investment>对象。所以你需要一个函数将RAII对象转换为所内含的原始资源。有两种方法:隐式转换和显示转换。

(1)显示转换

tr1::shared_ptr和auto_ptr都提供了一个成员函数get返回内部的原始指针,这是显式转换。

int days = daysHeld(pInv.get());    //好的,没有问题

(2)隐式转换

tr1::shared_ptr和auto_ptr都重载了操作符operator->和operator*,这样就允许隐式转换到原始指针。举例:假设Investment类有个成员函数bool isTaxFree() const;那么下面的调用是OK的:

bool taxable1 = !(pInv->isTaxFree());        //好的,没有问题
bool taxable2 = !((*pInv).isTaxFree());        //好的,没有问题

现在的问题是,需要原始指针的地方(例如,函数形参),如何以智能指针代替。解决方法是:提供一个隐式转换函数。下面举个字体类的例子:

FontHandle getFont();                    //取得字体句柄
void releaseFont(FontHandle fh);        //释放句柄
class Font
{
public:
    explicit Font(FontHandle fh) : f(fh){}
    ~Font()
    {
        releaseFont(f);
    }
private:
    FontHandle f;
};

如果C API处理的是FontHandle而不是Font对象,当然你可以像tr1::shared_ptr和auto_ptr那样提供一个get()函数:

FontHandle get() const { return f; }    //显示转换函数

这样是可以的,但客户还是觉得麻烦,这时候定义一个隐式转换函数是必须的。

class Font
{
public:
    ...
    operator FontHandle() const { return f; }
    ...
};

注意:假设你已经知道了隐式转换函数的用法。例如:必须定义为成员函数,不允许转换为数组和函数类型等。

完成了以上工作,对于下面这个函数的调用是OK的:

void changeFontSize(FontHandle f, int newSize);
Font f(getFont());
int newFontSize;
changeFontSize(f, newFontSize);        //好的,Font隐式转换为FontHandle了。

隐式类型转换也增加了一种风险。例如有以下代码:

Font f1(getFont());
FontHandle f2 = f1;        //将Font错写成FontHandle了,编译仍然通过。

f1被隐式转换为FontHandle,这时f1和f2共同管理某个资源,f1被销毁,字体释放,这时候你可以想象f2的状态(原谅我这个词我不会说),再销毁f2,必然会造成运行错误。通常提供一个显示转换get函数是比较好的,因为它可以避免非故意的类型转换的错误,这种错误估计会耗费你很长的调试时间(我遇到过的情况)。

请记住:

(1)有些API要求访问原始资源,所以每一个RAII class应该提供一个“取得其所管理的资源”的办法。

(2)对原始资源的访问可以通过显式转换或隐式转换。一般显式转换比较安全,但隐式转换对客户比较方便。



条款16:成对使用new和delete时要采取相同形式

我相信你肯定一眼看出以下代码的问题:

std::string *stringArray = new std::string[100];
delete stringArray;

程序行为是未定义的。stringArray所含的100个string对象,99个可能没被删除,因为它们的析构函数没被调用。

delete必须要知道的是:删除的内存有多少个对象,决定了调用多少个析构函数。

单一对象和数组的内存布局肯定是不同的,数组占用的内存也许包含一个“数组大小”的记录。(编译器干的事)你可以告诉编译器删除的是数组还是单一对象:

std::string *stringPtr1 = new std::string;
std::string *stringPtr2 = new std::string[100];
delete stringPtr1;        //删除一个对象
delete [] stringPtr2;    //删除一个数组

这个规则很简单,但有一点需要注意:

typedef std::string AddressLines[4];
std::string *pal = new AddressLines;

delete pal;                //不好,行为未定义。
delete [] pal;            //很好。

所以,最好不要对数组做typedef。

请记住:如果new表达式中使用了[],必须在相应的delete表达式中使用[];如果new表达式中不使用[],一定不要在相应的delete表达式中使用[]。



条款17:以独立语句将newed对象置入智能指针

假设有以下函数,具体含义我们可以先忽略:

int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);

先说明一点的是,当你以下面形式调用processWidget时,肯定是错误的:

processWidget(new Widget, priority());

编译出错。tr1::shared_ptr构造函数需要一个原始指针,但该构造函数是explicit,即禁止隐式类型转换的。所以,正常情况你应该这样调用:

processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority());    //好的,没有问题

以这种调用方式,执行函数体之前,有三个工作要做:

(1)调用priority。

(2)执行"new Widget"。

(3)调用tr1::shared_ptr构造函数

可以肯定的是(2)在(3)前面执行,但(1)的执行次序不能确定。假设(1)在第二个被执行,则如果调用priority出现异常,new Widget返回的指针将会遗失,因为还未执行tr1::shared_ptr的构造函数。所以,发生了资源泄露。

避免这类问题很简单:使用分离语句。

std::tr1::shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority());                //绝不会造成泄露

请记住:以独立语句将newd对象存储于智能指针内。如果不这样做,一旦异常被抛出,有可能造成难以察觉的资源泄露。

时间: 2024-11-07 14:16:49

《Effective C++》第3章 资源管理(2)-读书笔记的相关文章

Java 线程第三版 第四章 Thread Notification 读书笔记

一.等待与通知 public final void wait() throws InterruptedException 等待条件的发生. public final void wait(long timeout) throws InterruptedException 等待条件的发生.如果通知没有在timeout指定的时间内发生,它还是会返回. public final void wait(long timeout, int nanos) throws InterruptedException

《DirectX 9.0 3D游戏开发编程基础》 第一章 初始化Direct3D 读书笔记

REF设备 参考光栅设备,他能以软件计算方式完全支持Direct3D Api.借助Ref设备,可以在代码中使用那些不为当前硬件所支持的特性,并对这此特性进行测试. D3DDEVTYPE 在程序代码中,HAL设备用值D3DDEVTYPE_HAL来表示.该值是一个枚举变量.REF设备用D3DDEVTYPE_REF来表示.这种类型非常重要,你需要铭记,因为在创建设备的时候,我们必须指定使用哪种设备类型. COM(组件对象模型) 创建COM接口时不可以使用c++关键字new.此外使用完接口,应调用Rel

Java 线程第三版 第三章数据同步 读书笔记

多线程间共享数据问题 一.Synchronized关键字 atomic一词与"原子"无关,它曾经被认为是物质的最小的单元,不能再被拆解成更小的部分. 当一个方法被声明成synchronized,要执行此方法的thread必须先取得一个token,我们将它称为锁.一旦该方法取得(或者说是获得)锁,它将运行此方法然后释放掉(或者返回)此锁.不管方法时怎样返回的(包括通过异常)该锁会被释放. 二.Volatile关键字 如果变量被标示为volatile,每次使用该变量时都必须从主寄存器中读出

《C++primer》v5 第2章 C++基础 读书笔记 习题答案

2.1 int,long long ,short 可表示范围和占用内存空间不同.具体与计算机有关. 无符号类型只能表示0和正数,带符号类型可以表示负数,0,正数. float是单精度,一般占用4个字节,double是双精度,一般占用8个字节,它们可表示的数据范围也不相同. 2.2 利率用double,本金和付款用int 2.3 unsigned u=10,u2=42; cout<<u2-u<<endl; cout<<u-u2<<endl; int i=10,

《Java并发编程实战》第二章 线程安全性 读书笔记

一.什么是线程安全性 编写线程安全的代码 核心在于要对状态访问操作进行管理. 共享,可变的状态的访问 - 前者表示多个线程访问, 后者声明周期内发生改变. 线程安全性 核心概念是正确性.某个类的行为与其规范完全一致. 多个线程同时操作共享的变量,造成线程安全性问题. * 编写线程安全性代码的三种方法: 不在线程之间共享该状态变量 将状态变量修改为不可变的变量 在访问状态变量时使用同步 Java同步机制工具: synchronized volatile类型变量 显示锁(Explicit Lock

读《Effective C++》的感受以及对于读书笔记的看法和规划(个人感受)

楼主最近在整理两本书的读书笔记<Effective C++>和<TCP/IP详解卷1:协议>,这两本都是经典书,笔记也都有了一些了.因为主要是谈谈<Effective C++>,所以把这本书已经写完的目录列出来,这个目录不会再更新了. <Effective C++>目录: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti

《TCP/IP详解卷1:协议》第2章 链路层-读书笔记

章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 1.引言 从图1-4可以看出,在TCP/IP协议族中,链路层主要有三个目的: (1)为IP模块发送和接收IP数据报: (2)为ARP模块发送ARP请求和接收ARP应答. (3)为RARP发送RARP请求和接收RARP应答. TCP/IP支持多种不同的链路层协议,这取决于网络所使用的硬件,如以太网.令牌环网.FDDI(光纤分布式数据接口)及RS-232串行线路等. 2.以太网和IEEE 802封装 (1)以太网 以太网一般是指数字

《C++primer》v5 第12章 动态内存 读书笔记 习题答案

这一章暂时没写完,先留着以后再写. 在C++程序中,程序员可以给手动开辟内存,但是这块内存需要手动释放,不便管理,因此新标准提供智能指针类型来管理动态对象.它负责自动释放所指向的对象. shared_prt允许多个指针指向同一个对象 unique_ptr独占所指向的对象 weak_ptr是一个弱引用,指向shared_ptr所管理的对象 一些操作: p=q;//递减p的引用计数,递增q的引用计数 shared_ptr<T> p(q);//p是q的拷贝,递增q的引用计数 通过make_share

《C++primer》v5 第8章 IO库 读书笔记 习题答案

8.1.8.2 这一章不咋会啊.. istream &read(istream &is) { int a; auto old_state=is.rdstate(); is.clear(); is>>a; is.setstate(old_state); return is; } int main() { read(cin); return 0; } 8.3 读到eof或错误类型的时候 8.4 #include<fstream> using namespace std;

《python基础教程》第3章使用字符串 读书笔记

第三章:使用字符串 1.字符串格式化操作符是一个百分号 % 2.只有元组和字典可以格式化一个以上的值.列表或者其他序列只会被解释为一个值. 3.in操作符只能查找字符串中的单个字符. 4.字符串方法: ①find():find方法可以在一个较长的字符串中查找子串,它返回子串所在位置的最左端索引,如果没有找到则返回-1.这个方法还能提供起始点和结束点的范围(提供第二,第三个参数),范围包含第一个索引,但不包含第二个索引,这在python中是个惯例. ②join():这个方法用来连接序列中的元素(序