EffectiveC++ 第3章 资源管理

探讨C++中资源管理的重要性和方法,包括使用auto_ptr和shared_ptr避免内存泄漏,资源取得时机与初始化概念,以及如何正确使用new和delete。深入解析智能指针在资源管理中的角色。

我根据自己的理解,对原文的精华部分进行了提炼,并在一些难以理解的地方加上了自己的“可能比较准确”的「翻译」。

Chapter 3 资源管理

条款13: 以对象管理资源

有时即使你顺利地写了对应对象的delete语句,但是前面的区域可能会有一个过早的return语句或者抛出了异常.它们一旦执行,控制流绝不会触及delete语句,造成内存泄漏

事实上我们可以将需要的动态资源放进对象内,因为对象的析构函数会自动释放那些资源

c++对应的解决方案有 auto_ptr,也就是所谓的智能指针,它其实是类指针(pointer-like)对象,其析构函数自动对其所指对象调用delete,可一定程度地避免潜在的资源泄漏可能性

先假设一个名叫func的函数,它会new一个xx类型的对象并返回其指针,你可以这样使用auto_ptr :

std::auto_ptr<xx> pt(func());

这个简单例子示范两个关键想法:

---获得资源后立即放进管理对象; 管理对象运用析构函数确保资源被释放---

由于auto_ptr被销毁会自动删除所指之物,所以注意别让多个auto_ptr同时指向一个对象。后果是对象很可能会被删除一次以上,那将使你的程序搭上驶向“未定义行为”的列车上。

为预防这个问题,auto_ptr有一个性质: 若通过copying函数复制它们,它们会变成null,而复制所得指针将取得资源唯一拥有权:

std::auto_ptr<xx> pt1(func());
std::auto_ptr<xx> pt2(pt1); //现在pt2指向原本pt1所指对象,而pt1变成
//nullptr
pt1 = pt2; //现在pt1指向原本pt2所指对象,而pt2变成
//nullptr

可以看出auto_ptr的底层条件:受管理的资源必须绝对没有一个以上的auto_ptrs指向它。对于STL容器,这个特性不是很好。

替代方案是“引用计数型智能指针(reference-counting smart pointer)”,
shared_ptr
它其实也是一个智能指针,持续追踪共有多少对象指向某笔资源,::并在无人指向它时自动删除该资源。::

下面我们会将“引用计数型智能指针(reference-counting smart pointer)”的缩写RCSP来作为shared_ptr的别称。

对于RCSP,你可以这么写:std::shared_ptr<xx> pInv(func());

对于以下操作,RCSP相对于auto_ptr正常多了:

...
std::tr1::shared_ptr<xx> pInv1(func());
std::tr1::shared_ptr<xx>. pInv2(pInv1); //现在pInv2与1指向同一个对象
pInv1 = pInv2; //同上,无任何改变
...
//pInv1和pInv2被销毁后,它们所指的对象也就被自动销毁

综上,RCSP很适合STL容器的操作。

值得注意的是,auto和shared_ptr两者都在其析构函数内做delete而不是delete[]动作。这意味着在动态分配而得的array身上使用auto_ptr和tr1::shared_ptr是馊主意,然而这样会通过编译!

std::auto_ptr<std::string> aps(new std::string[10]);
std::tr1::shared_ptr<int> spi(new int[1024]);

条款14: 在资源管理类中小心copying行为

有可能你偶尔会发现,你需要建立自己的资源管理类

假设使用C API函数处理类型为Mutex的互斥器对象,共有lock和unlock两函数可用:

void lock(Mutek* pm); void unlock(Mutek* pm);

可能需要建立一个class来管理机锁:

class Lock{
public:
    explicit Lock(Mutex* pm)
        :mutexPtr(pm)
    { lock(mutexPtr); } //获得资源
    ~Lock() { unlock(mutexPtr); } //释放资源
private:
    Mutex* mutexPtr;
};

现在导入一个观念:

资源取得时机便是初始化时机(Resource Acquisition Is Initialization; RAII)
并以此作为“资源管理类的脊柱”

「客户」对Lock的用法符合RAII方式:

Mutex m; //定义需要的互斥器
...
{
    Lock ml(&m); //锁定互斥器
      ... 
    //在区块末尾解除锁定
}

此时假设Lock对象被复制:

Lock ml1(&m); 锁定m
Lock ml2(ml1); 将ml1复制到ml2身上,会发生啥?

大多数情况你会选择下面两种操作:

  1. 禁止复制. 许多时候允许RAII对象被复制并不合理,如果你发现不合理,就应该禁止之。
    根据条款6,我们发现可以:将copying操作声明为private.对Lock而言看起来是这样:
class Lock: private Uncopyable{
public:
    …
};
  1. 对底层资源祭出“引用计数法”. 有时我们希望保有资源,直到它的最后一个使用者(某对象)被销毁。此时复制RAII对象时应将该资源的“被引用数”递增。也就是使用tr1::shared_ptr

通常只需内含一个tr1::shared_ptr成员变量,RAII classes便可实现出reference-counting copying行为.

存在一个问题:shared_ptr缺省行为是“当引用次数为0时删除所指物”,然而我们想要的动作是解除锁定而非剔除。

幸运的是tr1::shared_ptr允许指定所谓的“删除器(deleter)”——当引用次数为0时便被调用,它是一个函数或函数对象,对于指针是可有可无的第二参数:

class Lock{
public:
    explicit Lock(Mutex* pm)
        :mutexPtr(pm,unlock)
        {
            lock(mutexPtr.get()); //以后谈到“get”
          }
private:
    std::tr1::shared_ptr<Mutex> mutexPtr;
};

注意,不再声明析构函数,因为没必要,编译器有缺省行为


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

假设有这种情况:

使用RCSP智能指针保存factory函数的调用结果:

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

假设有一个函数处理investment对象:

int dayHeld(const Investment* pi); //返回投资天数

而你想这么调用:

int days = daysHeld(pInv);

这样通不过编译,因为即使pInv指向的是investment对象,但是pInv本身是类型为tr1::shared_ptr的对象

这时你需要一个函数来将RAII class对象(本例为tr1::shared_ptr) 转换为其内含原始资源(本例为Investment*)。

有两种办法:显式转换和隐式转换。

tr1::shared_ptr和auto_ptr都提供一个叫get的成员函数,用来执行显式转换,它将返回智能指针内部的原始指针的复件 :

int days = daysHeld(pink.get())

这两种指针还重载了指针取值操作符(operator->和operator*),这将允许隐式转换至原始指针:

class Investment{
public:
    bool isTaxFree() const;
};
...
std::tr1::shared_ptr<Investment> pInv(createInvestment());
bool ok = pInv->isTaxFree();
bool ok1 = *(pInv).isTaxFree();
...

很多传统的做法便是xx.get()来获取内部资源,如果这种操作很繁琐的话,有一种更轻松的做法(隐式转换):

class XXX{
public:
    ...
    operator xxx() const    //隐式转换函数,xxx是返回类型
    { return f; }
    ...
};

比如某个函数参数原来得这么写

func(xx.get())

现在你可以写成func(xx)

但这种转换可能会引发错误:

假设类A里储存的是B类对象,当B类对象想拷贝一个A类对象时,却发生了隐式转换:

A obj(getA());
...
B obj2 = obj; //喔唷!原意是拷贝一个A对象,却将obj隐式转换为其底部的B对象了,然后再复制

很多时候显式转换更受欢迎


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

看一下这段代码:

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

很明显此程序行为不明确。对象数组所含的100个string对象中的99
个不太可能被适当删除,因为它们的析构函数很可能没被调用。

delete最大的问题在于:::即将被删除的内存之内究竟有多少对象?::这个问题决定了有多少对象的析构函数必须被调用起来。

也就是, 即将被删除的指针,所指的是单一对象或对象数组? 数组所用的内存通常还包括“数组大小”的记录,以便delete知道需要调用多少次析构函数。

所以记住,删除对象数组要用 delete[] xxx;

这种规则对typedef的使用也很重要:

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

此时AddressLines是一个数组,如果这样new:

std::string* pal = new AddressLines;

就必须匹配 delete[] pal

尽量不要对数组做typedef动作,你大可用STL中的vector等templates


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

假设有这样一个函数:

void process(std::tr1::shared_ptr<Widget> pw);

process将对传来的Widget对象运用智能指针
现在考虑调用函数:

process(new Widget);

嘿嘿,这是不能通过编译的。Widget对象需要被shared_ptr包装一下。因为tr1::shared_ptr构造函数需要一个原始指针(raw pointer),但该构造函数是explicit的,无法隐式转换,可以这么写:

process(std::tr1::shared_ptr<Widget>(new Widget));//实际上仍有风险

假设process函数有第二个参数func(),它是一个函数的返回结果:

process(std::tr1::shared_ptr<Widget>(new Widget),func());

调用process之前,编译器必须创建代码,做以下三件事:

  1. 调用func()
  2. 执行”new Widget”
  3. 调用tr1::shared_ptr构造函数

对于c++,此行编译时,有可能编译器先执行”new Widget”操作:

  1. 执行”new Widget”
  2. 调用func()
  3. 调用tr1::shared_ptr构造函数

然而如果此时func()调用导致异常,”new Widget”返回的指针将会遗失,因为它尚未被置入tr1::shared_ptr内,这将引发资源泄漏。

我们可以使用分离语句,将异常干扰减小:

std::tr1::shared_ptr<Widget> pw(new Widget);
process(pw,func());

OVER

转载于:https://www.cnblogs.com/1Kasshole/p/9092939.html

### 回答1: 《Effective C++ 第三版》是一本关于C++高效编程的经典书籍,它涵盖了23个关键的C++编程技术,旨在帮助C++程序员成为更加高效和高质量的软件工程师。本书主要聚焦于面向对象编程和模板编程,以及C++语言的一些独特特性和语法,如函数重载、拷贝构造函数、移动语义、虚函数和多重继承等。本书帮助读者了解和掌握如何更好地使用C++,以提高代码的可读性、可维护性和可重用性。本书还覆盖了很多经常出现的编程问题,如内存管理、异常处理、并发编程等等,以及如何进行程序优化和测试。《Effective C++ 第三版》是一本全面而且深入的书籍,可以帮助C++ programmer成为真正的C++专家。无论是入门开发者还是有经验的程序员,都可以从中获得很多价值。此外,《Effective C++ 第三版》也是一本非常好的参考书籍,可以为学习和使用C++提供坚实的理论基础和实用的示例代码。 ### 回答2: "Effective C++" 是著名的程序设计书籍,作者是著名计算机科学家Scott Meyers。该书现已经推出第三版,也有了相应的PDF电子书。 这本书主要介绍了C++语言的一些重要概念和技术,帮助读者更好地理解C++的基本特性和高级编程技巧。第三版对前两版的内容进行了全面更新和完善,加入了最新的C++11和C++14标准中的变化和增强,包含了作者多年的实践和经验总结,增加了大量的实例和代码。 此外,"Effective C++"第三版还介绍了许多经典的C++编程问题和解决方案,例如对象生命周期管理、内存管理、资源共享、模板和STL应用等。这些问题在实际编程中经常会遇到,掌握了这些技巧有助于提升代码的质量、可靠性和效率。 总之,"Effective C++"第三版是一本十分实用、经典的C++编程书籍,它不仅适合初学者,也对有经验的程序员有重要的参考价值。无论是想提高自己的编程水平、规避C++程序中常见的错误,还是想进一步掌握现代C++编程技术的读者,都不可错过这本书。 ### 回答3: Effective C++ 第三版 pdf 是一本非常有价值的书籍,它是由 C++ 大师 Scott Meyers 所著,是 C++程序员必读的一本书籍。本书深入浅出地介绍了 C++语言中的一些非常重要的知识点和技巧,帮助读者更好地理解和运用 C++语言,提高编程的质量和效率。 在 Effective C++ 第三版 pdf 中,作者从多个方面对 C++语言进行了详细分析和解释,包括构造函数和析构函数的实现,运算符重载的使用,继承和虚函数的原理和应用,以及 STL等一系列的 C++语言特性和库函数的使用技巧。通过对这些重要知识点的深入讲解,读者可以很好地掌握 C++语言的内涵和精髓,提高自己的编程能力和技巧。 除此之外,Effective C++ 第三版 pdf 还提供了大量的实例和代码,能够直观地展示作者所说的知识点,并帮助读者更好地理解和掌握 C++语言的实际应用。读者可以通过反复练习和实践,逐渐掌握 C++语言的精髓和技巧,并能够应用到实际开发中,提高自己的编程能力和水平。 总之,Effective C++ 第三版 pdf 是一本非常有价值的书籍,它涵盖了 C++语言的重要知识点和技巧,同时提供了大量的实例和代码,能够帮助读者全面掌握 C++语言,提高自己的编程能力和水平。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值