1

我已经实现了两种我认为应该如何实现单例类的方法,我只是想要程序员的意见,哪种方法是最好的方法。

每个方法都使用这些类:

class Animal {
public:
    virtual void speak() const = 0;
};

class Dog {
    virtual void speak() { cout << "Woof!!"; }
};

第一种方法:

class AnimalFactory {
public:
    static Animal* CreateInstance(int theTypeOfAnimal);

private:
    AnimalFactory() { };
    static int count; // this will be used to count the number of objects created
    static int maxCount; // this is the max count allowed.
};

int AnimalFactory::count = 0;
int AnimalFactory::maxCount = 1;

Animal* AnimalFactory::CreateInstance(int theTypeOfAnimal)
{
    Animal* pAnimal = NULL;

    if(pAnimal != NULL)
    {
        return pAnimal;
    }

    switch(theTypeOfAnimal)
    {
        case 0:
            pAnimal = new Dog();
            count++;
            break;

        case 1:
            pAnimal = new Cat();
            count++;
            break;

        case 2:
            pAnimal = new Spider();
            count++;
            break;

            default:
            cout << "Not known option";
    }

    return pAnimal;
}

第二种方法:

template<typename classType>
class Singleton {
public:
    classType& instance()
    {
        static classType object;
        return object;
    }
};

任何意见将不胜感激,谢谢:)!

4

8 回答 8

4

在大多数情况下,Singleton 被滥用。一般建议是:不要使用它!如果你认为你真的有一个单例是正确解决方案的情况:概率仍然对你不利。如果您真的非常确定需要使用 Singleton,您可能仍希望将其用作某种美化数据:不要。它会伤害你。

好的,您已被警告。

第二种方法在 C++ 2011 中具有明显的优势,即它是线程安全的。它也恰好更简单。

如果您还没有实现 C++ 2011 逻辑的编译器,并且您在多线程应用程序中使用 Singleton,那么您需要确保它只被初始化一次,即使从同时调用访问函数两个线程。当然,这会导致其他一些不起作用的情况:不要使用双重检查锁定模式。它[也]不起作用。如果您实现了它的工作原理,那么有人会出现并“修复”您的代码。

于 2012-10-29T21:18:56.090 回答
1

无论如何,在嵌入式环境中,您不需要单例模式,甚至不需要 new 或 malloc。

很难解释,但我试试。

嵌入式系统必须工作。没有配备手册和常见问题解答和互联网论坛的 IT 部门,没有人会调整你的 Linux 系统,没有人会修复你的配置,没有人会根据任务需要安装更大的硬盘或内存。您的程序无法使用有意义的错误代码向 stderr 和 exit() 写入详细消息,如果可以,则仅表示您的嵌入式系统无法工作。如果您可以在错误时闪烁红色指示灯,总比没有好,但它仍然意味着:设备失败。

我认为,不使用 malloc/free 和 new/delete是一个好习惯。您应该通过将对象定义为静态数组来“预分配”可能的最大对象数。或者,如果最大项目数取决于用户可以更改的配置,您应该在程序启动时分配固定数量的对象,因此启动时会出现内存不足的问题,而不是半小时后。

是的,会浪费很多内存,但不会出现神秘错误。而且您不必处理程序中的内存问题(仅在启动时)。

于 2012-10-30T14:01:52.877 回答
0

第一个解决方案有一些奇怪的编码(参见 Luchian Grigore 的回答)。忽略这一点:这是工厂和单例之间的一些概念混合。工厂通常是单例,但它通常发出的不是。

第二个确实确保您只能创建一个类的一个对象,但前提是您将其包装在 Singleton 模板中。如果该类具有公共构造函数,则可以在方案之外创建它,从而违反规则。

在我的书中,这些都不是做单例的好方法。

扩展一下:单例的想法是因为您需要管理一些特定的事情,并且您需要一个组件来完全负责。

在您的第一个版本中,您可以拥有多个工厂,每个工厂都创建一个动物。在现实世界中没有意义,在代码中也没有意义。

您的第二个示例尝试允许对(可选)的每个调用都是单例 - 但并非所有类都需要,并且许多类在“单例化”时不一定会表现。

于 2012-10-29T21:12:44.157 回答
0

看起来你可以通过创建一个函数来省去一大堆头痛:

Animal * CreateAnimal(int theTypeOfAnimal)
{
    static int count = 0;
    static int maxCount = 1;

    switch(theTypeOfAnimal)
    {
        // ...
    }
}
于 2012-10-29T21:12:48.720 回答
0

好吧,第一个不起作用:

Animal* pAnimal = NULL;
if(pAnimal != NULL)
{
    return pAnimal;
}

你期望这个条件永远成立吗?

我之前看过第二个,所以我会继续说它更惯用,至少对于这种类型的单例来说,这并不是一个真正的单例,因为你有多个实例,尽管每个 -实际型。好吧,一般的想法是好的(对于单身人士),但我会选择类似的东西:

template<typename classType>
class AnimalManager {
public:
   static Animal& instance()
   {
       static classType object;
       return object;
   }
};

你可以打电话给

Animal& animal = AnimalManager<Dog>::instance();
//assuming you derive Dog from Animal, which you're currently not
于 2012-10-29T21:12:49.983 回答
0

我会被大量的 Java 程序员贬低,但我不介意:如果您知道,您的程序只需要一个对象实例,请随意创建一个全局对象并编写一个静态 get() 方法,该方法返回(引用)它(并且不要从其他地方引用全局对象)。

我认为,Java 中的单例模式是一种实现全局对象的技术,在 C++ 中是一种隐藏全局对象的技术。但是为什么,隐藏起来也一样丑,不隐藏也一样丑。

你也应该小心地包裹它,让它像其他普通物体一样出现在场景中,并把它当作普通物体处理。也许,稍后,随着程序的发展,它们的实例会越来越多,你会很高兴它已经充当了一个普通的对象。

于 2012-10-29T21:31:53.717 回答
0

在回顾中,John Vlisides 说如果一个“模式”不应该出现在他的 GOF 书中,那就是单例。但是,另请参阅他对在 C++ 中实现它的讨论。

另请参阅 Andrei Alexandrescu,他使用 3 种不同的创建模型、4 种不同的生命周期模型和 2 种线程模型探索单例,这是实现单例的 24 种方式。

就个人而言,我通过只传递需要的对象来竭尽全力避免它们。它使测试更容易,使接口更明确,等等。你已经被警告过,当然你已经被警告过,但是你会听吗?

于 2012-10-29T21:33:31.580 回答
0

第一个代码片段未能实现单例,因为AnimalFactory::CreateInstance未能检查它维护的实例计数。

第二个代码片段,

template<typename classType>
class Singleton {
public:
    classType& instance()
    {
        static classType object;
        return object;
    }
};

如果做得好,在 Scott Meyers 之后被称为Meyers's singleton 。

给定的代码可以工作,但会产生笨拙的用法。为避免必须实例化,应声明Singleton该方法。此外,对类只能通过, 实例化的要求应记录在案,例如作为带有示例的注释。instancestaticclassTypeSingleton

其他人已经警告过你不要使用单例,但让我也这样做。

单例有其用途,例如确保仅创建一次 Windows API“窗口类”,但主要是它们被滥用为伪装的全局变量。全局变量的一个问题,即您不知道全局变量何时被初始化(在 C++ 编程中称为静态初始化顺序惨败),可以通过单例来避免。然而,主要问题仍然存在,即它们充当意大利面条式通信系统,在您永远不会怀疑的地方之间以无法追踪的方式路由引发混乱的信息。

所以,不要。

但是如果你发现你绝对需要单例,例如用于日志工具,那么请阅读 Andrei Alexandrescu 的“现代 C++ 设计”中关于单例的讨论,然后获取Loki 库并让它去做适合你的工作。

于 2012-10-30T05:00:13.523 回答