1 用 object 管理 资源: 资源管理 class / RAII class
1 `手动 释放 资源`
Factory Method 提供 A 继承体系 的 `动态分配对象`, caller 负责 释放 对象管理的资源
A* createA();
问题: 3 种 场景 + 2 个 泄露
————————————————————————————————————————————————————
1) 抛出异常
2) 过早 return
3) 循环内 delete 因某个 continue / goto 而 过早退出
————————————————————————————————————————————————————
————————————————————————————
`泄露
[1] 对象内存
[2] 对象 所保存的 资源`
————————————————————————————
|
| 解决
|/
2 `RAII: 用 对象 管理 资源 / put 资源 to 对象` + 依赖 C++ 的 `dtor 自动调用机制` 确保 `资源被释放`
|
|/
init. 初始化 -> 实际 也可以是 `赋值`
`SP ( smart pointer )`
————————————————————————————————————————————————————————————————————————————————————————————————
std::auto_ptr | [1] 多 auto_ptr 不能同时指向 同一 资源
| [2] `copying 函数 复制` auto_ptr 时, 转移所有权, `源 auto_ptr 变 null`
| `STL 容器 容不得 auto_ptr`
| `STL 容器 要求其 elem` 有 `not 转移所有权 的 复制行为`
————————————————————————————————————————————————————————————————————————————————————————————————
std::shared_ptr | [1] RC( 引用计数 )
| [2] 可启用第2参数 deleter
————————————————————————————————————————————————————————————————————————————————————————————————
相同 | `dtor 内 delete 单个 元素, 而不是 delete 数组 / delete []`
————————————————————————————————————————————————————————————————————————————————————————————————
3 令 factory Method 返回 SP 而不是 raw pointer`
`强迫 client 用 存储 raw pointer 的 SP`
std::shared_ptr<A> createA();
2 在 资源管理 class 中 小心 copying 行为
1 用 C API 处理 mutex
2 个 函数
——————————————————————————————————————————————
void lock(Mutex* pm); // 锁住 pm 所指 mutex
void unlock(Mutex* pm); // 解锁 mutex
——————————————————————————————————————————————
2 `client 按 RAAI 方式 使用 Lock`
Mutex m; // 1) 定义 mutex
...
{ // 2) 设 block 来定义 critical section ( `临界区` )
Lock lk(&m); // 3) 锁住 mutex
... // 4) 执行 `临界区` 内 操作
} // 5) block 末尾, 自动解除 mutex 锁定
3 `资源管理(RAII) 类`
——————————————————————————————————————————————————————————————————————————————————————————————
资源管理 class / resource handler
——————————————————————————————————————————————————————————————————————————————————————————
脊柱: RAAI
——————————————————————————————————————————————————————————————————————————————————————————
[1] 管理 heap-based 资源 | auto_ptr / shared_ptr
——————————————————————————————————————————————————————————————————————————————————————
[2] 管理 non-heap-based 资源 | 自建 `资源管理 class + shared_ptr 作 internal ptr`
| | |
| 如 | | 如
|/ | |/
mutex | Lock
——————————————————————————————————————————————————————————————————————————————————————————————
internal raw ptr: Mutex* => 管理的资源 是 Mutex
RAAI 对象 被 copy 时, 发生什么 ?
答: 4 种需求 & 处理
(1) 禁止 copy
`copy 行为` 对 RAAI class `不合理`
应用: Lock + private 继承 Uncopyable
(2) resource RC
想保持 resource, 直到 最后1个 RAAI object 被 销毁
RAAI class
`internal ptr 由 native ptr 改为 RCSP`
| |
|/ |/
Mutex* shared_ptr<Mutex>
|
| 问题: `shared_ptr 所指 Resource 的 RC = 0 时, 默认动作` 是
|/
delete 其 所指物`,
而我们目标 `释放动作` 是 `解除锁定`
|
| 解决: 启用 shared_ptr 第 2 参数
|/
deleter
function 或 function object ( unlock() )
RC = 0 时, 被 调用
Note: auto_ptr 无次机能
Note
该 RAAI class 不必 explicitly 声明 dtor
compiler implicitly 自动生成 的 dtor 版本
会 自动调用 其 成员 RCSP/shared_ptr 的 dtor
在 RC = 0 时, 自动调 deleter
(3) deep copy
copy RAAI 对象 + copy 底层 resource 同时进行
| |
| 如 |
|/ |/
某种 `字符串` `internal ptr 指向 heap 内存`
(4) `move ownership of 底层 resource`
希望 `只有 1 个 RAAI 对象` 指向 raw resource`
|
| 如
|/
auto_ptr
resource 的 ownership 转移: 从 源对象 转移到 目标对象
3 在 资源管理 class 中 访问 原始 resource
引入
`C API 通过 RAII class(标准库 或 自建) 的 explicit / implicit conversion
get() 成员函数 / operator-> 或 operator* 或 类型转换运算符
将 RAAI object 转换为 internal ptr/Handler to raw resource
进而访问 raw resource
(1) 资源管理 class / RAII 类 转换为 internal raw handle/ptr to resource
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
[1] explicit conversion | | get() 成员函数 | [1]优: 安全
| | | [2]缺: 麻烦
———————————————————————— shared_ptr ——————————————————————————————————————————————————————————————————————————————————————————————
| auto_ptr | 2种 解引用 运算符 | std::shared_ptr<A> sp1( createA() ); // A* createA();
| | operator-> | bool isTrue = !( sp1->isF() ); // A::isF()
| | operator* | bool isTrue2 = !( (*sp2).isF() );
[2] implicit conversion ——————————————————————————————————————————————————————————————————————————————————————————————————————————————————
| | | [1]优: 方便 C API 调用
| 自建 RAII 类 | 类型转换运算符 | [2]缺: 易出错
| | | copy RAII 对象, 误写为 隐式转换为 其 internal Handle 的 copy,
| | X::operator T() | RAII对象 销毁时, resource 释放 + `目标 handle/ptr 悬挂`
| | | RAIIClass rAIIObj( getAHandle() );
| | | InternalHandle internalHandle = rAIIObj;
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
4 new / delete pair 形式要相同
1 new/delete 背后: 3/2 条 基本操作
2 delete
(1) 问题
若用错了 delete 形式 => undefined behavior
(2) Note
single object 和 object array `内存布局` 不同
object array 的 内存 含 `array size record`
3 `class 的 internal ptr 指向 动态分配内存` + 有 `多个 ctor` 时
所有 ctor 中 要用 `同形式 new` 初始化 internal ptr
|
|/
都 new / 都 new []
原因: dtor 只有 1 个 => 只能有 1 种 delete 形式
4 `不要用 array 形式 做 typedef`
| 避免 用错 delete 形式
|
| 替换为
|/
标准库中 `string / vector 等`
可 `将 array 需求 降为 几乎为 0`
5 以 独立语句 存 new 的 object 到 智能指针
`compiler` 有 `重排 同一 statement` 各 `基本操作` (的 执行次序) 的 自由
| |
| |
|/ |/
f( shared_ptr<Widget>( [1] new Widget
new Widget ), getVal() ); [2] shared_ptr ctor
[3] getVal()
void
f(std::shared_ptr<Widget> spw, |
int val); |
|
|/
若 编译器 重排 函数调用语句 f(.) 为 [1] -> [3] -> [2]
+ [3] 抛出异常
=> `new 返回的 ptr 丢失 / 未被置入 shared_ptr 内`
=> `资源泄漏`
|
| 解决
|/
`分离 2个操作 于 不同 statement`
compiler 对 `跨 statement 的 各操作` 没有 `重排 其 基本操作` 的自由
std::shared_ptr<Widget> pw( new Widget );
f( pw, getVal() );
=== 详细
概述
1 资源
`使用后必须还给系统` 的 东西
C++ 中 常用资源
——————————————————————————————————————————
`动态分配 的 内存`:
`分配了 却 未曾归还`, 导致 `内存泄露`
file descriptor
mutex lock
数据库连接
网络 socket
——————————————————————————————————————————
2 `手动 归还资源` 的 问题
|
|/
非 compiler 自动 调 dtor 等 方式
1) 异常
2) 提前 return / 跳出 (因 循环内的 continue / goto)
等 导致 归还未被执行
3 以 对象 管理资源
以 ctor / dtor / copying 函数
严格遵守这些做法, 可以几乎消除 资源关联问题
1 用 object 管理 资源: 资源管理 class / RAII class
1 手动 释放 资源
Factory Method 提供 A 继承体系 的 `动态分配对象`, caller 负责 释放 对象管理的资源
A* createA();
void f()
{
A* createA();
...
delete pInv;
}
问题: 3种场景 + 2个泄露
————————————————————————————————————————————————————
1) 抛出异常
2) 过早 return
3) 循环内 delete 因某个 continue / goto 而 过早退出
————————————————————————————————————————————————————
————————————————————————————
`泄露
[1] 对象内存
[2] 对象 所保存的 资源`
————————————————————————————
|
| 解决
|/
`2 RAII: 用 对象 管理 资源 / put 资源 to 对象` + 依赖 C++ 的 `dtor 自动调用机制` 确保 `资源被释放`
|
|/
init. 初始化 -> 实际 也可以是 `赋值`
`SP ( smart pointer )`
(1) std::auto_ptr
限制
1> `auto_ptr 销毁时, 自动删除 其 所指之物`
=> 多 auto_ptr 不能同时指向 同一 资源
否则, delete 多次 => undefined behavior
2> `用 copying 函数 复制` auto_ptr 时,
`源 auto_ptr 变 null`, `目的 auto_ptr 获得 资源 唯一拥有权`
`STL 容器 容不得 auto_ptr`
`STL 容器 要求其 elem` 有 `not 转移所有权 的 复制行为`
std::auto_ptr<A>
pInv1( createA() );
std::auto_ptr<A>
pInv2(pInv1); // pInv2 指向 object, pInv1 = null
pInv1 = pInv2; // pInv1 指向 object, pInv2 = null
|
| 引入
|/
RC(reference-counting)
追踪 有多少个 RCSP object 指向 资源,` 并在 `最后1个 RCSP 销毁时, 自动删除 资源`
|
| 如
|/
(2) std::shared_ptr
void f()
{
std::shared_ptr<A>
pInv1( createA() );
std::shared_ptr<A>
pInv2(pInv1); // pInv2/pInv1 均指向 object
pInv1 = pInv2; // 同上
} // pInv1/pInv2 两者 最后一个销毁时, 其 所指对象 被自动销毁
Note
auto_ptr 和 shared_ptr `相同点`
`dtor 内 delete 单个 元素, 而不是 delete 数组 / delete []`
=> `不该` 用于 `动态分配数组`
std::auto_ptr/shared_ptr<int> sp(new int(10) );
标准库 没 设计 `动态分配 数组` 的 SP
|
| reason
|/
`vector 和 string 几乎` 总是可以 `取代 动态分配数组`
3 conclusion
(1) 手动 释放资源 易出错
(2) 资源管理 class
标准 库 std::auto_ptr / std::shared_ptr
往往能 轻松满足 本条款 忠告
有时需自己定制
`(3) 令 factory Method 返回 SP 而不是 raw pointer`
`强迫 client 使用 存储 raw pointer 的 SP`
std::shared_ptr<A> createA ();
2 在 资源管理 class 中 小心 copying 行为
`1 non-heap-based 资源管理: 自建 资源管理类`
(1) 用 C API 处理 mutex object
2 个 函数:
void lock(Mutex* pm); // 锁住 pm 所指 mutex
void unlock(Mutex* pm); // 解锁 mutex
(2) 设计 RAII 类 Lock 来 管理 Mutex
确保 被锁住 的 Mutex 定会被 解锁
class Lock
{
public:
explicit Lock(Mutex* pm)
: mPtr(pm)
{ lock(mPtr);}
~Lock() { unlock(mPtr); }
private:
Mutex* mPtr;
};
(3) `client 按 RAAI 方式 使用 Lock`
Mutex m; // 1) 定义 mutex
...
{ // 2) 设 block 来定义 critical section (临界区)
Lock lk(&m); // 3) 锁住 mutex
... // 4) 执行 critical section (临界区) 内 操作
} // 5) block 末尾, 自动解除 mutex 锁定
(4) 但, Lock 对象 被 copy 时, 会 发生什么 ?
Lock lk1(&m);
Lock lk2(lk1);
问题一般化为: `RAAI 对象 被 copy, 会发生什么 ?`
`2 RAAI object 被 copy 时 的 需求 和 处理`
(1) 禁止 copy
class Lock: private Uncopyable // 禁止 copy
{
public:
... // 如前
};
(2) resource RC + 启用 shared_ptr 第 2 参数 deleter 去 执行 释放操作
class Lock
{
public:
explicit Lock(Mutex* pm)
: mSp(pm, unlock)
{ lock( mSp.get() ); /* get: 获得 internal raw ptr */ }
private:
std::shared_ptr<Mutex> mSp;
};
3 在 资源管理 class 中 访问 原始 resource
自定义 RAII class + internal raw pointer/Handle to resource
resource: 字体 <- internal Handle: AHandle <- RAAI class: A
C API
void changeASIze(AHandle ah, int size);
explicit conversion
get() 成员函数
AHandle get() const { return ah; };
client 每次 用 API, 就要调 get() -> 到处显式转换 -> 麻烦
changeASIze(A.get(), newASize);
implicit conversion
A::operator AHandle() const
{ return ah; }
client 调 C API 时 轻松自然
changeASIze(A, newASize); // A 隐式转换为 AHandle obj
但 隐式转换 增加错误发生机会
RAIIClass rAIIObj( getAHandle() );
// ...
InternalHandle internalHandle = rAIIObj;
client 原意是 copy RAII object, 却 误写了:
RAII object 隐式转换为 其 internal Handle 的 copy,
该 copy 再 assignment 给 新 internalHandle
=> 新 internalHandle 被 销毁时, resource 被释放
=> 新 internalHandle 成 `dangle/悬挂`
`1 资源管理 class` 可对抗 `资源泄漏`
[1] 通常 `必须 用 资源管理 class 处理 与 resource 间 交互`
`API 不应 直接 访问 raw resource`
[2] 有时 `需要 用 API 处理 resource`
int dayHeld( const A* pI );
`SP 如 auto_ptr 和 shared_ptr 可用于
保存 factory Method return 的 raw pointer to resource`
std::shared_ptr<A> pInv( createA() );
只要 用1个 函数 可
将 RAAI class object 转换为 其 internal raw ptr to resource
auto_ptr 和 shared_ptr 用 `get() 成员函数 / operator-> operator*`
可实现该 explicit / implicit conversion
int days = dayHeld(pInv.get() );
class A
{
public:
bool isF() const;
};
A* createA();
std::auto_ptr<A>
std::shared_ptr<A> sp1(createA() ); // A* createA();
bool isTrue = !( sp1->isF() ); // A::isF()
bool isTrue2 = !( (*sp2).isF() );
2 自建 RAAI class
类型转换运算符
`将 class type 值` 转换为 `其他 type 值`
X::operator T()
mem func
return_type 即 operator 后的 type
paraList 空
```
// 不准确实现
(1) Resource raw Handle/ptr
class AHandle {};
(2) C API
AHandle getAHandle() { return AHandle(); }
void releaseResource(AHandle ah){ }
void changeASize(AHandle ah, int newSize){ }
(3) RAAI 类
class A // RAII class
{
private:
AHandle ah; // raw Handle/pointer to resource
public:
explicit A(AHandle ah_) // 获得资源, pass by value 因为 C API 也这么做
: ah(ah_) {}
~A() { releaseResource(ah); } // 释放资源
AHandle get() const { return ah; } // 显式转换
operator AHandle () const { return ah; } // 隐式转换
};
int main()
{
A a( getAHandle() );
int newASize = 10;
changeASize(a.get(), newASize);
changeASize(a, newASize);
}
3 conclusion
`(1) C API 往往要 访问 raw resource
=> `RAAI class 应` 提供 `access 其 所管理资源` 的 方法/interface
`(2) acess resource 可经由 explicit conversion ( 较 安全 ) 或 implicit conversion ( 对 client 较 方便 )`
4 new / delete pair 形式要相同
image.png
`1 new/delete 背后: 3/2 条 基本操作`
new
1) 分配内存 operator new
2) 强转 为 class type
3) 针对此内存 调 class ctor
delete
1) 针对此内存 调用 class dtor
2) 释放内存 operator delete
`2 delete`
(1) 问题
若用错了 delete 形式 => undefined behavior
(1) 若对 strPtr1 用 delete 数组 形式 ( delete [] p )
delete 读 若干 memory 解释为 array size = n + 调 n 次 析构 + 释放 n 块 object memory => undefined behavior
(2) 若对 strPtr2 用 delete 元素 形式 (delete p ) => 只析构1次 => undefined behavior
(2) Note
single object 和 object array `内存布局` 不同
object array 的 内存 含 `array size record`
用于告诉 delete, 应该
`调 多少次 dtor`
`释放 多少个 object 的 memmory`
对 `client` 来说, 只需要 `用 delete 数组的形式: delete [] ptr,
std::string* strPtr1 = new std::string;
std::string* strPtr2 = new std::string[100];
...
delete strPtr1;
delete [] strPtr2;
3 `class 的 internal ptr 指向 动态分配内存` + 有 `多个 ctor` 时
所有 ctor 中 要用 `同形式 new` 初始化 internal ptr
4 `不要用 array 形式 做 typedef`
typedef std::string arr[3];
std::string* strPtr = new arr; // <=> new string[3]
delete strPtr; // undefined behavior
delete [] strPtr; // ok
arr 类型 可设为 vector<string>
5 以 独立语句 store newed object in 智能指针
网友评论