13-以对象形式管理资源
目的:
确保控制流在离开 单一区块 或 函数 的时候, 保存对象的资源 会自动调用析构函数释放此资源,避免内存泄漏。手动使用delete会很不安全且容易忘记。
方法
把一个资源放到对象里面,依赖析构函数自动调用机制,在其离开作用域后自动调用delete 删除
在申请后立即将资源放到智能指针 管理对象中,智能指针销毁时会自动删除它所指之物所申请的资源。
- auto_ptr :一个资源永远只有一个auto_ptr指向
当有第二个auto_ptr 指向的时候,第一个会失效
- shared_ptr:一个资源可以有多个shared_ptr指向
内部包含了一个计数器,当无人指向资源时会调用析构函数
智能指针缺点:不能指向数组,因为内部调用的是delete 而不是 delete[ ]。
我们可以用string 或者 vector 来代替
知识点扩展
RAII: 在资源获取时就对其进行初始化
环状引用 :两个对象互相引用对方,彼此互指share_ptr 无法打破这个状态
需要再使用weak_ptr :如果一个对象仅仅需要引用另一个对象而不拥有它,它不会增加对象的引用计数,也不会影响对象的生命周期,从而避免循环引用导致的内存泄漏。
[overfold]
#include <iostream>
#include <memory>
class B; // 前置声明
class A {
private:
std::weak_ptr<B> b_ptr; // 使用 weak_ptr 避免循环引用
public:
void setB(const std::shared_ptr<B>& ptr) {
b_ptr = ptr;
}
void display() {
std::cout << "Class A" << std::endl;
}
};
class B {
private:
std::weak_ptr<A> a_ptr; // 使用 weak_ptr 避免循环引用
public:
void setA(const std::shared_ptr<A>& ptr) {
a_ptr = ptr;
}
void display() {
std::cout << "Class B" << std::endl;
}
};
int main() {
// 创建 shared_ptr 对象
std::shared_ptr<A> objA = std::make_shared<A>();
std::shared_ptr<B> objB = std::make_shared<B>();
// 设置对象间的引用
objA->setB(objB);
objB->setA(objA);
// 当 main 函数结束时,由于使用了 shared_ptr 和 weak_ptr,不会产生循环引用导致的内存泄漏
return 0;
}
14-RAII 资源管理类中要小心copying行为
知识点扩展
heap-based资源:可以指任何在堆上动态分配的资源,比如动态分配的内存块、对象、数据结构等
Reference counting copy:是指在引用计数智能指针(如 std::shared_ptr)的实现中,当进行对象的拷贝时,不是简单地复制指针本身,而是复制指针所指向对象的引用计数,并增加该引用计数。
目的
如果一个资源并不是 heap_based 类 那么就不适合用智能指针。需要自己建立自己的资源管理类
方法
复制RAII 会选择两种处理
-
禁止复制:调用
class lock : private Uncopyable隐藏RAII类的复制行为 -
使用shared_ptr 对管理的资源进行计数
//shared_ptr 可以指定“删除”方式,可以是一个函数或函数对象)
void lock(Mutex* pm) {} // 锁定pm所指的互斥器
void unlock(Mutex *pm){}; // 将互斥器解除锁定
class Lock2 {
public:
explicit Lock2(Mutex* pm) : mutexPtr(pm, unlock) { // 以某个Mutex初始化shared_ptr,并以unlock函数为删除器
lock(mutexPtr.get()); // 获得资源
}
private:
std::tr1::shared_ptr<Mutex> mutexPtr; // 使用shared_ptr替换raw pointer
};
int main()
{
// -----Demo 1.-----
Mutex m; // 定义你需要的互斥器
Lock2 m1(&m);
Lock2 m2(m1);
//m1 m2 同时指向 Mutex m当 main 函数结束时,m1 和 m2 对象会自动销毁,从而导致 Mutex 对象 m 的引用计数减少。由于指定了 unlock 函数作为删除器,所以在 Mutex 对象的引用计数减少到零时,unlock 函数会被调用来解锁并释放 Mutex 资源
return 0;
}
复制RAII对象必须复制它所管理的资源,包括指针和所指的内存。进行的是"深拷贝"
例:复制指针就必须将指针所指的东西也复制,不然就成空悬指针了
15-在资源管理类种提供原始资源的访问
目的
因为许多API 需要直接引用原始资源,所以不得不跳过RAII (生产者模式)
使用原始资源的方法
给RAII提供一个访问原始资源的方法 。返回值为原始资源对原始资源访问
- 显示转换比较安全(推荐)
- 隐式转换比较方便:但是不安全
智能指针重载了指针取值 -> * 允许隐式地转换至原始指针
void releaseFont(FontHandle fh) {}
class FontHandle {
};
class Font {
public:
explicit Font(FontHandle fh) : f(fh) { // 获得资源,采用pass-by-value,因为C API这样做
}
~Font() {
releaseFont(f); // 释放资源
}
FontHandle get() const {
return f; // 显式转换函数
}
operator FontHandle() const {
return f; // 隐式转换函数
}
private:
FontHandle f; // 原始字体资源
};
FontHandle getFontHandle() {
FontHandle fh;
return fh;
}
int main()
{
Font f(getFontHandle());
// int newFontSize = changeFontSize(f.get()); // 将Font显式转换为FontHandle
int newFontSize = changeFontSize(f); // 将Font隐式转换为FontHandle
Font f1(getFontHandle());
FontHandle f2 = f1; // 错误!原意是要拷贝一个Font对象,却反而将f1隐式转换为其底部的FontHandle,然后才复制它。
}
16-使用new[ ] 的时候 也要使用delete[ ]
扩展
typedef string address[4];//它创建了一个类型别名 address,用于表示一个包含四个 string 元素的数组
address myAddress;
myAddress[0] = "123 Main St.";
myAddress[1] = "Unit 4";
myAddress[2] = "Anytown";
myAddress[3] = "Anystate";
目的:
要告诉delete删除的是一个数组还是单个对象
方法
如题
造成后果:太少析构函数为被调用。或者更多无关内存中的对象被删除
17-以独立的语句将对象置入智能指针
processWight(std::tr1::share_ptr<Wight>(new Wight) pw, priority());
//引用上述代码有三种方式
processWidget(new Widget, priority()); // 错误,函数要求是shared_ptr<Widget>,而new生成的是Widget*
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority()); // 容易造成内存泄露
当程序以 new Wight -> priority() -> share_ptr Wight(new Wight) 的顺序执行形参的时候
一旦priority()出现错误,就会导致 new 出来的 Wight 内存泄漏。(不同编译器有不同的顺序 ,不易察觉)
所以要分开创建,也可以直接使用make_shared 隐式转换指针
[processWidget(std::make_shared<Widget>(), priority()); // 使用 std::make_shared 创建智能指针,并进行隐式转换
std::tr1::shared_ptr<Widget> pw(new Widget); // 在单独语句内以智能指针存储new所得对象](<std::share_ptr Wight pw (new Wight);>)
processWight(pw, priority());

6315

被折叠的 条评论
为什么被折叠?



