前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >单例看起来很美,但...

单例看起来很美,但...

作者头像
Linux兵工厂
发布2024-04-15 13:43:43
670
发布2024-04-15 13:43:43
举报
文章被收录于专栏:Linux兵工厂Linux兵工厂

START

自从C++11 保证了静态局部变量的线程安全性之后,单例在C++11 里就简单得可怕:

代码语言:javascript
复制
class Singleton {
public:
    Singleton(const Singleton&) = delete;
    Singleton& operator = (const Singleton&) = delete;

    static Singleton& instance(){
        static Singleton instance;        // #1
        return instance;
    }

private:
    Singleton() = default;
    ~Singleton() = default;
};

代码#1 是关键。所以C++11 之后单例经常被使用,一般情况下用起来确实很爽,但是也会引起一些问题。

第一个问题:跨动态库的问题。

如果两个so 都引用一个单例,一般情况下会在两个so 里各产生一个单例,虽然gcc下可以通过编译选项让单例跨so的时候仍然只会有一个实例,但是,对于clang编译器是无解的。解决办法有两个,各自的so里使用自己的单例,二者不相关的时候是可以的;还有一个办法是让其它so的单例依赖某个so的单例,自己建立依赖关系,保证不同的so 只依赖一个单例,就是比较麻烦。

第二个问题:单例生命周期依赖的问题。

如果有两个单例A和B,其中B 依赖了A,在B的析构函数里需要访问A,这时候可能会有这样一个场景,先创建B的单例,再创建A的实例,然后B析构的时候再访问A单例,这时候就会出现一个访问错误:访问了已经释放的单例A。这是一个典型的单例依赖问题,最近社区报了一个yalantinglibs 的一个issue就是这样的。

代码语言:javascript
复制
// Static de-initialization order problem
// File1.h
class A {
....
  void doSomething() {
    ...
  } 
}
extern A aObj;

//File1.cpp

static A aObj;

// File2.cpp

class B {
B() {}
~B() {
 aObj.doSomething(); // Not okay! aObj may have already been destructed!
}
....
}

static B bObj;

因为C++里的静态对象的初始化规则是这样的:"The order in which static objects are initialized across different translation units is undefined or ambiguous."

简言之不能保证静态变量的初始化顺序。那应该怎么解决这个单例生命周期依赖的问题呢?

也很简单,利用另外一条规则:Construct on first use idiom。如果你能控制第一次创建单例的顺序,那么就能解决这个问题。比如你先创建A单例,再创建B单例,A比B先创建,那么A 会比B 后析构,这时候在B的析构函数里访问单例A 是安全的。

代码语言:javascript
复制
// Static initialization order problem
// File1.h
class A {
....
  void doSomething() {
    ...
  } 
};

A& aObj();

//File1.cpp

A& aObj() {
  static A *aObj = new A();
  return *aObj; 
}

// File2.cpp

class B {
 B() {
   /*
    * Okay since calling aObj() gaurantees that
    * static A *aObj = new A(); ran
    */
   aObj().doSomething();  
  }

  ~B() {
    aObj().doSomething(); // Okay! it's safe!
  }
};

static B bObj;

很简单的一个改动,在B的构造函数里先创建A 单例,这样就能保证A比B先创建,B 析构的时候A 还在,访问A是安全的,从而解决了单例生命周期依赖的问题。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-04-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Linux兵工厂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档