C++中类的全局实例化

12

正如标题所述。我该如何创建一个全局可用的类实例(例如,我有一个用于打印的函数对象,我希望有一个单一的全局实例(虽然可以创建更多))。


这个之前的问题只是StackOverflow上单例模式的一个例子:https://dev59.com/XXVC5IYBdhLWcg3whBaj - Mark Ransom
7个回答

22

通过使用常规模式制作单例对象的所有努力都没有解决你问题的第二部分——需要时能够创建更多实例。 单例“模式”非常限制,不过是另一个名字的全局变量而已。

// myclass.h

class MyClass {
public:
    MyClass();
    void foo();
    // ...
};

extern MyClass g_MyClassInstance;

// myclass.cpp

MyClass g_MyClassInstance;

MyClass::MyClass()
{
    // ...
}

现在,在任何其他模块中,只需包含myclass.h并像往常一样使用g_MyClassInstance。如果您需要进行更多操作,则可以调用准备好的构造函数。


1
这会导致问题,如果 MyClass 依赖于某个其他全局实例先被初始化,因为全局变量的初始化顺序是“随机”的。最好使用常规类+(创建单个实例)工厂类。 - richq
@rq:这不是随机的,请参见https://dev59.com/EnVC5IYBdhLWcg3wcgqd。 - Martin York
1
随机,未定义,在编译时无法确定...其实差不多。 - richq
2
如果我这样做 MyClass = MyClass() 会发生什么? - J.Doe

5

首先,你想要全局变量是一个“代码异味”(Per Martin Fowler的说法)。

但是为了达到你想要的效果,你可以使用一种单例模式的变体。
使用静态函数变量。这意味着变量在使用时才会被创建(这样可以实现懒加载),并且所有变量将按照创建的相反顺序被销毁(因此保证了析构函数会被使用)。

class MyVar
{
    public:
        static MyVar& getGlobal1()
        {
            static MyVar  global1;
            return global1;
        }
        static MyVar& getGlobal2()
        {
            static MyVar  global2;
            return global2;
        }
        // .. etc
}

6
按照这个逻辑,std::cout 也是一种代码坏味道。 - rr-
@rr-: 或者阅读关于“全局可变状态为什么不好”的数百万篇文章之一:http://programmers.stackexchange.com/questions/148108/why-is-global-state-so-evil - Martin York
@rr-:或者观看一些关于全局可变状态不好的Google Talks - Martin York
3
我不确定单例模式的问题与我的关于std::cout的观察有何关联。std::cout是好还是坏?如果它不是坏的,为什么?(注意 - 我认为它不是坏的,因为如果是的话,人们就会想出其他替代方案。) 我不确定单例模式的问题与我的关于std::cout的观察有何关联。std::cout是好还是坏?如果它不是坏的,为什么?(注意 - 我认为它不是坏的,因为如果是的话,人们就会想出其他替代方案。) - rr-
显示剩余3条评论

1
作为单例模式的轻微修改,如果您还想允许创建具有不同生命周期的更多实例的可能性,只需将构造函数、析构函数和operator=公开。这样,您可以通过GetInstance获得单个全局实例,但也可以在堆或栈上声明相同类型的其他变量。
基本思想是单例模式。

1

0

0

最简单且具有并发安全性的实现是Scott Meyer的单例模式:


#include <iostream>

class MySingleton {
public:
    static MySingleton& Instance() {
        static MySingleton singleton;
        return singleton;
    }
    void HelloWorld() { std::cout << "Hello World!\n"; }
};

int main() {
    MySingleton::Instance().HelloWorld();
}

请参阅第四个主题这里,了解来自GoF名人John Vlissides的分析。


1
它不是并发安全的。在C++标准中,当构造函数结束时,静态变量会“初始化”,因此在初始化时进入Instance()方法的每个线程都将导致单例被重新创建。 - Dmitry Khalatov
Dmitry Khalatov @ 技术上是正确的。有几个简单的解决方案。a)使用gcc,它有一个明确的修复方法来处理这个(非标准)问题。b)在任何线程之前创建对象。c)在Instance()方法内部使用锁。 - Martin York
我同意 @Dmitry 的观点:在标准提供处理线程存在的方法之前,它是不安全的。 - xtofl

0

我更喜欢允许单例模式,但不强制使用它,因此从不隐藏构造函数和析构函数。这已经被提过了,我只是在表达我的支持。

我的方法是,除非我想创建一个真正的单例并隐藏构造函数,否则通常不使用静态成员函数。我的常规做法是这样:

template< typename T >
T& singleton( void )
{
   static char buffer[sizeof(T)];
   static T* single = new(buffer)T;
   return *single;
}

Foo& instance = singleton<Foo>();

为什么不使用T的静态实例而是使用放置new?静态实例提供了构造顺序保证,但没有销毁顺序。大多数对象按照构造的相反顺序进行销毁,但静态和全局变量除外。如果您使用静态实例版本,最终会在main函数结束后出现神秘/间歇性的段错误等问题。

这意味着单例的析构函数永远不会被调用。然而,进程无论如何都会关闭并且资源将被回收。这可能有点难以适应,但请相信目前没有更好的跨平台解决方案。幸运的是,C++0x已经对保证销毁顺序进行了更改,可以解决这个问题。一旦您的编译器支持新标准,只需升级单例函数以使用静态实例即可。

此外,在实际实现中,我使用boost来获取对齐内存,而不是普通的字符数组,但不想使示例复杂化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接