单例模式的多种形态 (二)

在某些情况下,我们可能需要对多个类实现单例模式,一个个写的话实际上是一种重复,有没有什么方法可以只实现一次单例而能够复用其代码从而实现多个单例呢? 很自然的想到用模板或者继承的方法。

基类模板的实现要点是:

  1. 构造函数需要是 protected,这样子类才能继承;
  2. 基类的析构函数可以不需要 virtual ,因为子类在应用中只会用 Derived 类型,保证了析构时和构造时的类型一致

既然有继承,那么需要调用派生类的构造函数,但我们又需要构造函数是public,同时还要保证派生类的唯一性。找到了一种很复杂的写法,已经有些看不懂了。使用一个代理类 token,派生类构造函数需要传递token才能构造,但是把 token保护其起来,然后派生类的构造函数就可以是公有的了,这个派生类只有 Derived(token)一种形式的构造函数,而token是唯一的,这样用户就无法自己定义一个类的实例了,实现了派生类对象的唯一性。

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
template<typename T>
class Singleton{
public:
static T& get_instance() noexcept(std::is_nothrow_constructible<T>::value){
static T instance{token()};
return instance;
}
virtual ~Singleton() =default;
Singleton(const Singleton&)=delete;
Singleton& operator =(const Singleton&)=delete;
protected:
struct token{}; // helper class
Singleton() noexcept=default;
};


class DerivedSingle: public Singleton<DerivedSingle>
{
public:
DerivedSingle(token){
std::cout<<"destructor called!"<<std::endl;
}

~DerivedSingle(){
std::cout<<"constructor called!"<<std::endl;
}
DerivedSingle(const DerivedSingle&) = delete;
DerivedSingle& operator =(const DerivedSingle&) = delete;
};

int main(int argc, char* argv[]){
DerivedSingle& instance1 = DerivedSingle::get_instance();
DerivedSingle& instance2 = DerivedSingle::get_instance();
return 0;
}


Qt中的全局指针

Qt里有一个全局指针qApp,在任意地方都能使用,看看是不是单例模式。

QApplication中:

1
#define qApp (static_cast<QApplication *>(QCoreApplication::instance()))

QCoreapplication中:

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
//头文件中
#define qApp QCoreApplication::instance()

static QCoreApplication *instance() { return self; }

static QCoreApplication *self;

//源文件中
QCoreApplication *QCoreApplication::self = 0;
// 构造函数
QCoreApplication::QCoreApplication()
{
d_func()->q_ptr = this;
d_func()->init();
QCoreApplicationPrivate::eventDispatcher->startingUp();
}

// 就是 d_func()->init();
void QCoreApplicationPrivate::init()
{
......
Q_ASSERT_X(!QCoreApplication::self, "QCoreApplication", "there should be only one application object");
QCoreApplication::self = q;
......
}

从QCoreapplication来看,qApp是个宏,实际是函数QCoreApplication::instance(),QCoreApplication这个类十分关键,构造函数肯定不能是private。从这个self来看,特别像懒汉模式,self是在QCoreApplication构造函数里赋值,赋给它的q指针实际就是QCoreApplication的this指针。

但是在程序里使用qApp,你会发现其地址都一样,也就是同一个全局指针,这就在于Q_ASSERT_X这句限定了只能有一个QCoreApplication对象,再加上拷贝构造函数和赋值运算符都在QObject限定为private,因此qApp也是一种单例模式。所以我们可以说单例模式不一定限定构造和析构是private,这个使用了Qt特有的d指针和q指针,技巧性太高,还是用meyers模式吧

ROS中的单例模式

看ROS源码中的类TopicManager,它用到了单例模式,我模仿写了一个类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class SingleTon;
typedef boost::shared_ptr<SingleTon> SingleTonPtr;

#define Ptr SingleTon::getInstance()

class SingleTon
{
public:
SingleTon() {}
static const SingleTonPtr& getInstance()
{
static SingleTonPtr f = boost::make_shared<SingleTon>();
return f;
}
void out() { cout<<" out put "<<endl; }
private:
int m_num;
};

结果发现构造函数只能是public,如果是private,就会报错,原因在make_shared中。这样一来就不能实现单例了,看来这种做法不可行。


参考:
C++ 单例模式总结与剖析