c++资源管理经验谈

前言

编写中…

Step1: 用对象管理资源

Step2: RAII原则来申请和释放资源

  • 在构造的时候申请资源,在析构的时候释放资源

Step3: 管理好对象的生命周期

管理好对象的生命周期,对于单线程环境来说比较好办,注意几点就行了,但是对于多线程环境就比较难处理了。下面首先讲一些单线程环境下对象生命周期中需要注意的事项:

构造:

  • 不要在构造函数中暴露this指针,典型的不要调用观察者或者访问者接口,因为这个时候对象还没有构造完,不应该被调用

析构:

  • 不要在析构函数中抛出异常,异常会改变程序执行的顺序。
  • 父类析构函数应该定义为virtual,否则子类独有的资源不会被释放.

拷贝构造:

调用拷贝构造的时机:

  • 用一个对象构造另一个对象 Point fei();Point lun(fei);
  • 参数传递使用传值的方式的时候
  • 函数返回类型值的时候
  • Point fei = lun;(不会调用赋值操作符,只会调用拷贝构造)

赋值:

首先回顾一下赋值操作符的四个要点:

  • 检查是否为同一个地址if(this == &rhs) return *this
  • 参数为类型的常引用const Feature& rhs
  • 返回为*this
  • 在分配资源之前先回收清理原有的资源
1
2
3
4
5
Point fei();
Point lun = fei; /// 只会调用拷贝构造函数

Point foo();
foo = fei; /// 只会调用赋值操作符

多线程下对象生命周期的管理

聚合资源与组合资源

结论:

  1. 使用对象来管理资源,使用RAII原则来申请和释放资源
  2. 对于聚合资源对象的类,使用依赖注入和shared_ptr来构造
  • RAII(资源申请和释放更安全)
  • 依赖注入(构造更块,调用安全)

依赖注入和控制反转
依赖注入是一种将对象传入到某个类中的(注入),而不是让这个类自己创建并存储该对象的技巧,可将其作为控制反转概念的一种更为特殊的形式。举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
class RedisDataSource
{

RedisConnection* poDriver_;
Vector<OGRLayer*> layers_;

public:
RedisDataSource():
poDriver_(new RedisConnection("host=192.168.1.23 port=5432 db=2"))
{
/// 其他操作
}
}

上面的代码存在以下问题:

  1. 如果RedisConnection的构造函数发生了变化,或者修改了数据库的端口,那么就必须修改RedisDataSource来解决
  2. 从销量来看,RedisDataSource的构造过程较长,切每个RedisDataSource事例都需要创建一个RedisConnection对象
  3. RedisDataSource强依赖于RedisConnection的定义

然而如果采用依赖注入的方式:

1
2
3
4
5
6
7
8
9
10
11
12
class RedisDataSource
{

RedisConnection* poDriver_;
Vector<OGRLayer*> layers_;

public:
RedisDataSource(RedisConnection* poRedis):
poDriver_(poRedis)
{
/// 其他操作
}
}
  1. RedisDataSource只需要前置生命RedisConnection即可,依赖性相对较低
  2. 可以由RedisDataSource的工厂来创建RedisConnection并注入到RedisDataSource中,工厂负责维护RedisDataSource状态的正确性

对于上面依赖注入的例子,我们将Redis链接当作是资源的话,用对象来管理资源没有问题(RedisConnection类),使用依赖注入的方式还可以让对个RedisDataSource实例公用一个RedisConnection对象。但是这就要保证共享的RedisConnection生命周期的安全,也就是上面的Step3的要求,具体点就是资源对象在正确的时候析构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class RedisDataSource : public DataSource
{
// RedisConnection* poDriver_;
std::shared_ptr<RedisConnection> poDriver_;

Vector<OGRLayer*> layers_;

public:
RedisDataSource(RedisConnection* poRedis)
{
/// 其他操作
if(poRedis)
{
poDriver_ = std::make_shared<RedisConnection>(poRedis);
}
}

// 用于共享本类的连接
RedisConnection* getConnection() const
{
return poDriver_.get();
}
}

class DataSourceFactory
{

public:
static DataSource* CreateDataSource(std::string dsType, std::string connInfo)
{
DataSource *poRet = NULL;
if(dsType == "redis")
{
RedisConnection* poRedisCon = new RedisConnection(connInfo);
if(poRedisCon)
{
poRet = new RedisDataSource(new RedisConnection(connInfo));
}
}
return poRet;
}

static DataSource* CreateDataSource(std::string dsType, Connection* poConn)
{
DataSource *poRet = NULL;
if(dsType == "redis")
{
if(poConn && poConn.getName() == "redis")
{
poRet = new RedisDataSource(new RedisConnection(connInfo));
}
else
{
// ...
}
}
else
{
// ...
}
return poRet;

}
}
1
2
3
4
5
6
RedisConnection* poRedis = 
DataSourceFactory::CreateDataSource("redis", "host=192.168.1.44...");

// 共用链接
RedisConnection* poRedis2 =
DataSOurceFactory::CreateDataSource("redis", poRedis.getConnection);

关于内存管理

C++中可能出现的内存问题:

  • 缓冲区溢出
  • 空悬指针/野指针
  • 重复释放
  • 内存泄漏
  • 不配对的new/delete
  • 内存碎片

关于类型安全

从指向父亲的指针转为指向孩子节点的指针

Reference

  • 《c++ API设计》by Martin Reddy
  • 《企业应用架构》by Martin Fowler
  • 《effective c++》
  • 《Linux多线程服务器编程》 by 陈硕