0%

以独立语句将newed对象置入智能指针

看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
class Widget { ... };

int priority() { ... }

void processWidget(std::shared_ptr<Widget> pw, int priority) { ... }

int mian() {
...;
processWidget(std::shared_ptr<Widget>(new Widget), priority());
...;
}

在调用processWidget函数之前,编译器创建代码会做三件事:

1、调用priority

2、执行”new Widget”

3、调用std::share_ptr的构造函数

但是C++编译器以什么样的次序完成这件事是不确定,唯一确定的顺序是”new Widget”一定在调用std::share_ptr的构造函数之前,编译器有可能在第二步执行调用priority函数。如果此时priority函数的调用发生异常,此时”new Widget”返回的指针可能将会遗失,此时可能引发资源泄漏。为了避免这个问题,最好把创建Widget的语句单独拿出来:

1
2
3
4
5
6
void main() {
...;
std::shared_ptr<Widget> pw(new Widget);
processWidget(pw, priority());
...;
}

成对使用new和delete时要采用相同形式

如果你在new表达式中使用[ ],必须在相应的delete表达式中也使用[ ]。如果你在new表达式中不使用[ ],一定不要在相应的delete表达式中使用[ ]。

另外尽量不要对数组形式做typedef动作,否则会导致new和delete的不匹配。例如:

1
2
3
4
5
typedef std::string Address[4];//用四个字符串来表示地址

std::string *pA = new Address;

delete pA;//应该使用 delete [] pA;

在资源管理类中提供对原始资源的访问

std中的shared_ptr和unique_ptr都提供了对原始资源对象的访问函数get()。在自己设计的资源管理类中也要提供一个访问原始资源的函数。

在资源管理类中小心copy行为

由于在资源管理类中存在指向动态内存的指针,在copy资源管理类时,动态资源的拷贝行为要根据实际情况来处理。所以需要对资源管理类的copy行为进行规定。

对于资管管理类的copy行为一般有以下几种处理办法:

  • 禁止复制
  • 使用引用计数法,通常只要内含一个shared_ptr成员变量即可
  • 复制底部资源(深拷贝)
  • 转移底部资源的拥有权

动态资源的copy属性决定了资源管理类的copy属性

以对象管理资源

该条款针中的资源指的是用户动态申请的资源。看一个例子:

假设有一个工厂函数,用于生产某特定的Investment对象

1
2
3
4
5
6
7
8
9
10
11
12
class Investment { ... };

Investment* createInvestment() {
return new Investment();
}

int main() {
...;
Investment* p = createInvestment();
...;
delete p;
}

在上例中,如果用户不知道createInvestment的内部实现,那么就不知道Investment是动态创建出来的,另外如果用户知道createInvestment是动态创建出来的,也很有可能忘记了要delete。所以好的方式是利用非动态对象来管理动态申请的资源,利用非动态对象的析构函数来释放资源。

1
2
3
4
5
int main() {
...;
std::shared_ptr<Investment> pInv(createInvestment());
...;
}

这种其实还有一个问题,就是用户很有可能忘记使用share_ptr来管理,所以最好直接让工厂函数返回一个shared_ptr。

1
2
3
std::shared_ptr<Investment> createInvestment() {
return share_ptr<Investment>(new Investment());
}

通常使用shared_ptr和unique_ptr就可以完成对动态申请的资源进行管理,但是某些情况下可能需要自己设计一个用于管理动态资源的对象

复制对象时勿忘记其每一个成分

如果自己不定义copy构造函数和copy assignment操作函数的话,编译器提供的copy构造函数和copy assignment操作符函数会自动对其基类部分进行copy构造和copy assign,但是如果自己定义了copy构造函数和copy assignment操作函数的话,那么就要在这两个函数中对基类进行复制。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Base {
public:
std::string name;
};

class Derived: public Base {
public:
int age;
//注意copy构造函数是在成员初始化列表中调用基类的copy构造函数
//而copy assignment操作符函数是在函数体内调用基类的copy assignment操作符函数
Derived(const Derived &another): Base(another), age(another.age) {};

Derived& operator=(const Derived &another) {
Base::operator=(another);
age = another.age;
}
}

在operator= 中处理“自我赋值”

使用operator=对自己进行赋值首先没有意义,其次,如果存在动态申请的资源的话,可能会产生一些问题,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class AClass {
private:
int *data;
public:
AClass() {
data = new int[100];
}

int* getData() {
return data;
}

AClass& operator=(const AClass& another) {
delete data;
data = another.data;
return *this;
}

};

如果在operator=函数中的another和*this是同一个对象,那么就会导致自身的数据被删除。

所以应该在operator=中避免自我赋值

通常处理自我赋值有一下几种实现方式:

//TODO:看了29条后再来补充

令operator= 返回一个指向自身的引用

为了实现“连续赋值”,赋值操作符应该返回一个指向自身的引用。

1
2
3
4
5
6
7
8
class AClass {
public:
void operator=(const AClass& aObject);//不能实现连续赋值
AClass& operator=(const AClass& aObject);//可以实现连续赋值
};

AClass a, b, c;
a = b = c;//连续赋值

绝不在构造和析构过程中调用virtual函数

  • 在derived class构造中的base class构造期,调用的virtual函数不会是派生类对应的实现,原因是因为虚表指针的初始化时机造成的。虚表指针会在进入到构造函数体之前被初始化,在进入到base class的构造函数体之前,虚表指针被赋值为指向base class的虚函数表,在进入到derived class的构造函数体内之前才会被赋值为指向derived class的虚函数表。
  • 另外在derived class对象的base class构造期间,对象的类型是base class而不是derived class。若使用运行期类型信息例如dynamic_cast和typeid,也会把对象视为base class类型。

别让异常逃离析构函数函数

//说实话有点没懂这一点,有点

就是说在析构函数中就要对异常进行处理,不能rethrow?

多态基类声明virtual析构函数

在使用c++多态特性的时候,通常会用基类指针或者基类引用来指向派生类对象,如果派生类对象是动态创建出来的,当delete的时候传入的是基类指针,如果析构函数是non-virtual的话,那么就可能出现属于派生类对象的部分未被析构的情况。因此,任何class只要带有virtual函数都几乎确定应该有一个virtual析构函数

但是无端地将所有class的析构函数函数声明为virtual,也是错误的。在c++中所有STL容器如vector,list,set,map等都是不带virtual析构函数的class

某些情况将析构函数声明为pure virtual函数

比如有时候你希望拥有抽象class,但是手上没有任何pure virtual函数时,可以将该类的析构函数声明为pure virtual函数,然而这里有个窍门:你必须为这个pure virtual析构函数提供一份定义,因为抽象class的析构函数仍会在派生类的析构函数中被调用,如果不提供定义,那么连接器就会报错。

总结

给base class一个virtual析构函数,这个规则只适用于有多态性质的base class

如果不想使用编译器自动生成的函数,就该明确拒绝

如果不想让编译器生成默认的copy构造函数和copy assignment函数,可以使用c++11的新特性

1
2
3
4
5
class UncopyableClass{
public:
UncopyableClass(const UncopyableClass& uncopyableClass) = delete;
UncopyableClass& operator=(const UncopyableClass& uncopyableClass) = delete;
};