EffectiveC++(3)(13~17)
13:以对象管理资源
以对象管理资源的两个关键想法:
- 获得资源后立刻放进资源管理对象内
- 管理对象应用析构函数确保资源被释放
考虑:
class test{
public:
test* createTets(); //创建一个指向对象的指针
void f(); //用于删除使用完毕之后的对象
};
void test::f()
{
test* newTett = createTets();
//...
delete newTett;
}
这样的做法看似稳妥,但是假如在...部分有return语句使函数提前结束,则资源无法释放,久而久之造成内存泄漏。
有时候也会有无我们个人的原因,忘记析构某个对象之类的,为此我们的解决方案是利用对象管理资源
如使用 shared_ptr 或 unique_ptr.
但仍要注意,在使用shared_ptr时可能会造成的错误。
如:
#include <memory>
#include <iostream>
int main()
{
int a = 52;
{
std::shared_ptr<int> test(&a);
}
std::cout << a;
}
在test退出程序块时会释放掉内存,因此之前定义的变量a就不能使用了,需时刻注意。
注意:shared_ptr在析构时调用的是delete而不是delete[ ],若想改变这一状况可自行传入删除器
请记住:
- 为防止资源泄漏,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源
- 两个常用的RAII类为shared_ptr与unique_ptr,前者通常为较佳选择,因为其复制行为比较直观,若选择unique_ptr,复制动作会报错,但是可以通过右值来转移所有权(即std::move)
14:在资源管理类中小心copying行为
当一个RAII对象被复制,可以的选择是:
-
禁止复制
有些RAII对象的复制并不合理,可以使用=delete来禁止这类对象的辅助。
-
对底层资源使用"引用计数"
在希望保有资源,知道其最后一个使用者被销毁。这种情况下使该资源的"引用计数"递增。
使用shared_ptr可以为一个资源管理类方便地添加一个引用计数功能,当shared_ptr析构时所做的操作不是你所想要的时候,可以为其提供一个删除其来提供合理的删除操作。
-
复制底部资源
使用"资源管理类"的唯一理由时确保在不再需要时被释放,此时复制资源管理对象,也应复制其所包覆的资源,即"深拷贝"。
-
转移底部资源的拥有权
自己编写拷贝函数,以免编译器合成的默认函数造成不符合预期的效果。
15:在资源管理类中提供对原始资源的访问
在一些API中直接指涉资源,除非永远不会使用这样的API,否则只得绕过资源管理对象而直接访问原始资源。
类shared_ptr与unique_ptr都提供给了一个函数get(),用于返回智能指针的原始指针。
而shared_ptr与unique_ptr都重载了"->"与".",它们允许隐式转换至底部原始指针。
class Investment
{
public:
bool isTaxFree() const;
};
Investment* createInvestment(); // factory函数
std::shared_ptr<Investment> pi1(createInvestment());
bool taxablel1 = pi1->isTaxFree(); // operator ->
std::unique_ptr<Investment> pi2(createInvestment());
bool taxablel2 = (*pi2).isTaxFree(); // 右operator*访问资源
由于有时候还是需要取得RAII对象内部的原始资源,为此可以设置一个显示get,例如:
class ResourceManage
{
public:
int *get()
{
return a;
}
private:
int *a;
};
// 调用
ResourceManage a;
invoke(a.get());
这可能会造成大量的get调用。
或为类提供隐式转换函数
class NeedType
{};
class ResourceManage
{
public:
operator NeedType() const
{
return a;
}
private:
NeedType a;
};
// 调用
ResourceManage a;
invoke(a);
但这可能增加出错几率,可能会造成在赋值时调用隐式转换而造成预期之外的结果。
请记住
- API往往要求访问原始资源,所以每一个RAII类都有提供一个获取其所管理资源的方法。
- 对原始资源的访问可能经由显示转换和隐式转换,一般而言显示转换比较安全,而隐式转换比较方便。
16:成对使用new和delete时要采用相同的形式
当使用new生成对象时,会:
- 分配内存
- 在此内存上调用构造函数
而当使用delete删除对象时,会:
- 在此内存上调用析构函数
- 释放此内存
然而如果对数组使用delete而非delete [ ],则会造成未定义行为。
这常常意味着只删除了单一对象,数组内的其余对象未被删除。
同样的,对单一对象调用delete [ ],也会造成未定义行为。
因此如调用new时使用了[ ],则调用delete时也需要使用[ ],如果调用new时未使用[ ],则调用delete时也不该使用[ ]
此外,考虑
typedef std::string AddressLines[4];
std::string* pal = new AddressLines;
则删除pal时必须使用[ ]:
delete pal; //未定义
delete [] pal; // 好
因此,最好不要对数组形式使用typedef。
17:以独立语句将newed对象置入智能指针
考虑:
int priority();
void a(sharded_ptr<Obj> pw, int p);
a(new Obj, priority()); // 无法通过编译,因为shared_ptr需要指针的构造函数是一个explicit函数
a(shared_ptr<obj>(new obj), priority()); // 虽然通过编译,但是可能造成资源泄漏
为什么第二种方式可能会资源泄漏呢?
在以第二种形式调用时,需要做以下三件事:
- 调用priority
- 执行"new obj"
- 调用shared_ptr构造函数
但是由于C++编译器的自由性,编译器优化后可能会产生这样的顺序:
- 执行"new obj"
- 调用priority
- 调用shared_ptr构造函数
假如在调用priority时抛出异常,则new obj返回的指针不会被置入shared_ptr中,从而造成资源泄漏。
解决该问题的办法就是将new语句分开来写。
shared_ptr<obj> pw(new obj);
a(pw, priority());
请记住
- 以独立语句将newed对象存储入智能指针内,如果不这样做,一旦一场被抛出,则有可能产生难以察觉的资源泄漏。

浙公网安备 33010602011771号