【问题标题】:C++: Replacement for SingletonC++:单例的替代品
【发布时间】:2011-07-14 04:14:07
【问题描述】:

我经常使用单例,因为我讨厌将主类的对象传递给成员类以允许它们访问主类。

Class Foo
{
    A* a;
    B* b;
    C* c;
};

例如在上面的示例中,如果 A、B 和 C 想要访问 Foo- 我必须将 Foo 的对象传递给他们每个人,并将其存储为他们的成员变量(或在每个函数中给出对象称呼)。这可行,但感觉不对,需要编写更多代码。

相反,我可以让 Foo 成为单例(当然只有在只有 1 个实例的情况下),然后从 A、B 和 C 调用 Foo::getInstance()->...。而我没有传递任何对象。我觉得这很方便。

现在,问题是我遇到了一个可能有多个 Foo 实例的情况。显然我不能使用单例模式。但我不想传递变量,也不想将它们存储在成员类中。代码太多! :)

例如, 我讨厌这个:

A::A(Foo* foo) : m_foo(foo)
{

}

还有这个:

void A::someFunc(Foo* foo, int someParam)
{

}

但是,我喜欢这个:

A::A()
{

}

void A::someFunc(int someParam)
{
    Foo* foo = Foo::getInstance();
}

还有其他方法吗?类似于单例模式的东西?

【问题讨论】:

  • 我不明白你为什么首先在 C++ 中使用单例。
  • @ptrdiff_t 您要解决的实际问题是什么?对我来说,你想要一些看起来像单例但仍然有多个实例的东西是没有意义的。发布“不方便”的代码将帮助我更好地了解您想要避免的事情。
  • 使用单例乍一看似乎不错,但实际上它们会使您的代码变得更糟:- 它们使您的代码变得不那么清晰(代码以隐式方式依赖于上下文)- 它们使代码难以测试 - 它们使代码难以维护,主要是因为未来的变化它可能适用于小示例,但大项目应尽可能避免使用单例。
  • 阿格。停止。停止。停止。去读一本关于编程的书或参加关于编程的课程。现在似乎使事情变得容易的东西只会使编程的所有其他部分变得异常困难。停下来。请记住,您的代码的维护者知道您住在哪里并且他拥有一把斧头。
  • 我怀疑由于使用全局变量/单例而导致的几个 24 小时马拉松式调试会话,这些会话的状态以意想不到的方式发生了变化,这可能会改变您对它们有用性的看法。

标签: c++ class singleton


【解决方案1】:

这种模式会创建一堆循环引用,这通常是代码异味。您可能想仔细查看您的设计,因为解决此类关系问题的常用方法是创建与现有类交互的第三个类。或者,如果您确实需要这种行为,那么传递对包含类的引用有什么问题?

【讨论】:

  • 其实没什么问题——我只是懒。但似乎我必须这样做......是的,这绝对是一种代码味道,但经常发生,因为重构会做很多工作。
【解决方案2】:

让你的单身class 成为template

template<unsigned int NUMBER>
class Foo
{
  A* a;
  B* b;
  C* c;
};

并使用任何你想要的实例。它仍将保持单例,但您可以拥有多个对象:

Foo<1>;
Foo<2>;
Foo<3>;

【讨论】:

  • 是的,我明白这一点,但是当我在 A、B 或 C 类时,我不知道 Foo 的哪个实例中有我作为成员。
  • @ptrdiff_t,您可能需要相应地微调您的代码。我的建议只是使用模板的想法;你会比我更了解更大的画面。 :)
【解决方案3】:

我已经在几个项目中做到了这一点。下面是一些伪代码,让您了解我是如何实现它的:

static map<string, Foo*> instances;

static Foo* Foo::getInstance(string name)
{
    Foo* inst = NULL;

    lock(instances);
    if(instances.count(name) > 0)
    {
        inst = instances[name];
    }
    else
    {
        inst = new Foo();
        instances[name] = inst;
    }

    unlock(instances);
    return inst;
}

然后你只需调用 Foo::getInstance("instance1") 或 Foo::getInstance("instance2") 等。你所要做的就是记住一个字符串,有点好(我认为)。我不知道这个设计模式有没有正式的名字,如果有人请告诉我,这样我以后描述的时候就不会那么无知了。

【讨论】:

  • 是的,我也想过这种实现方式,但是 A、B、C 无法知道哪个实例初始化了它们 :)
  • 好的,我明白你在说什么,但是到那时,无论你想出什么解决方案,你都必须告诉 A、B 和 C 一些事情 i> 关于他们的创建者,这违反了您的初始约束。我不相信有任何答案。
  • 是的,这就是我害怕的事情。只要确定是否有任何方法可以轻松完成。我非常喜欢这种模式..
  • 一个对象在没有明确告知的情况下知道它的创建者是不可能的。你现在这样做的方式有点作弊,你或多或少是在说“假设这个特定的实例是你的创造者”。您可能会探索工厂模式,它可能会根据您的需要为您做,但是无论您如何切割它都需要明确告知。
【解决方案4】:

单身人士是邪恶的,我们都同意。但原因往往被遗忘。这并不是因为它们本质上是全球性的。全局变量在解决问题时很棒。单例的问题在于它们将生命周期、初始化顺序和全局访问结合在一起。

听起来你需要一个全局变量,所以使用一个全局变量。

A* g_a;
B* g_b;
C* g_c;

在其他需要访问它们之前初始化 main 中的所有全局变量。

或者做得更聪明。

template< typename T >
T& instance( void );

template< typename T >
void set_instance( T& t );

void needs_an_A( void )
{
   instance<A>().a_stuff();
}

或者最好将它与 RAII 结合起来:

void needs_a_b( void )
{
   B& b = instance<B>();
   b.stuff();
   b.more_stuff();
}

int main()
{
   Initializer<A> init_a;
   Initializer<B> init_b;  // B needs an A during construction
   Initializer<C> init_c( "C constructor param" );

   needs_a_b();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-21
    • 2010-12-10
    • 2013-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多