一、问题
在原来的文章中分析过使用全局或者静态智能指针时需要的注意的情况(“静态或者全局智能指针使用的注意几点”)。特别是在一些动态赋值的情况下,很容易一不小心就二次释放导致程序的崩溃。那么,静态和全局的智能指针到底可不可以用?怎么用比较好呢?
先说一下结论,不建议在程序中使用全局智能指针(不管是否为静态),局部静态智能指针需要控制好使用,这个没有什么问题。
二、例程
先看一下例程:
#include <iostream>
#include <thread>
using namespace std;
#include <memory>
class A {
public:
A() { std::cerr << "call A constructor!" << std::endl; }
virtual ~A() { std::cerr << "call A destructor!" << std::endl; }
};
class B : public A {
public:
B() { std::cerr << "call B constructor!" << std::endl; }
~B() { std::cerr << "call B destructor!" << std::endl; }
public:
int getdata() { return 10 + 30; }
};
class C {
public:
C() { std::cerr << "call C constructor!" << std::endl; }
~C() { std::cerr << "call C destructor!" << std::endl; }
static std::shared_ptr<C> get() {
static auto x = std::make_shared<C>();
return x;
}
void test() { std::cerr << "call C test function!" << std::endl; }
public:
// A a;
// B b;
};
//普通全局
std::shared_ptr<B> bp = std::make_shared<B>();
//二次释放崩溃
char buf[12] = {0};
auto pbuf = std::shared_ptr<char[]>(buf);
int main() {
auto x = C::get();
x->test();
auto x1 = bp->getdata();
return 0;
}
这个程序在运行结束时会崩溃然后报二次释放指针的异常。
三、分析
为什么不建议在全局使用智能指针?一个最主要的原因就是全局可见的智能指针往往不受控的被所有人发现并可以重新赋值,这个实在是太可怕了。就如上面的代码,如果有开发者觉得可以用这个全局指针来传递某些数据而不用再创新一个,多方便啊。结果可想而知,程序进入了极其危险的状态。
设计开发中有一个要求是自耦合,在软件设计中一般的原则为高内聚,低耦合,其实可以把自耦合放到高内聚中。自耦合其实就是接口隔离,而不是这样放着一个全局变量让它人去使用。不过在有些特殊情况下,可能需要一个全局的智能指针,这就需要开发者自己设计一些方法,让它的暴露性尽量降低并且在使用中尽量在保证功能的前提下受限。
说得更直白一些,不要过于相信自己的自律性别人的自律性,也就是说,自己写代码一定会时时刻刻保持清醒的头脑和风险意识,这种可能性是非常低的。另外还有对第三方的调用和一些知识的不匹配,一些认识上的不到位,这都是要求尽量降低开发风险的紧迫需求。正如一个笑话,女王招一个马车夫,有四个车夫来应聘,女王问,如何才能在危险的悬崖边上行驶。有三个都说自己车技高超,驭马有术。只有一个车夫说,自己从不把马车赶到悬崖边上,然后女王就录取了他,明白了吧。
另外就是伪二次析构的问题,即在析构函数中调用了一些释放的变量,导致全局(或其它)变量析构时调用了其引起崩溃或者多次定义同一类型变量在析构时产生崩溃(参看前一篇文章),造成误会。这种情况在写测试代码和与其它人混用工程时,会有发生,这种情况还比较难发现。所以还是要注意。
四、总结
在实际的开发,尽量不要在构造和析构函数中做比较复杂的动作,比如开辟内存之类的东西。如果有必要建议专门有一个初始化函数。同样,析构也是如此,如果需要处理更多的东西,建议还是有一个专门的类似stop之类的函数来清理业务逻辑,然后再释放相关资源。智能指针更是如此(毕竟复杂的对象中内部对象释放顺序的不同有可能引起一些异常),越是不需要自己动手控制的,越是要尽量让其简单,这样才会更安全。
一个好的习惯可能看上去代码不是多么优雅,但工作起来确实相对优雅许多,安全性的增加,是每个程序员的工作的前提。特别对于C/C++程序员,动不动就崩溃,这不是一个好现象。好多事情,说起来容易,做起难,大家都明白,所以还是从细节抓起,不断将代码的安全性提高。