使用命名空间而不是单例

时间:2016-09-05 17:44:52

标签: c++ class oop c++11 singleton

最近我在SO上发布了一个关于类的使用的问题,该类具有一些理论上应该具有的独立功能。我被建议学习单例模式,以便只创建一个类的实例,它管理围绕它封装的数据的操作集。您可以在此处查看问题 - using Static Container for base and derived classes

现在考虑一下这段代码 -

#include <iostream>
#include <string>
#include <unordered_map>

class A{
    std::string id;
  public:
    A(std::string _i): id(_i){}
    virtual void doSomething(){std::cout << "DoSomethingBase\n";}
};

class B : public A{
    std::string name;
  public:
    B(std::string _n):name(_n), A(_n){}
    void doSomething(){std::cout << "DoSomethingDerived\n";}
};

namespace ListA{
    namespace{
        std::unordered_map<std::string, A*> list;   
    }
    void init(){
        list.clear();
    }
    void place(std::string _n, A* a){
        list[_n] = a;
    }
}


int main() {
    ListA::init();
    ListA::place("b1", new B("b1"));
    ListA::place("a1", new A("a1"));
    return 0;
}

如果程序没有像那样终止,忽略了我仍在使用泄漏内存的原始指针的事实,这是使用全局静态变量的一个很好的替代方法,还是单身?

关于上一个问题,我重新组织了A类(基类)和B类(派生类),独立于管理这些对象列表的命名空间。这是一个好主意,还是一个完全不好的做法?它有什么缺点吗?

我建议的一个好的单例实现如下 -

class EmployeeManager
{
    public:
        static EmployeeManager& getInstance()
        {
            static EmployeeManager    instance; // Guaranteed to be destroyed.
                                  // Instantiated on first use.
            return instance;
        }
    private:
        EmployeeManager() {};
        std::unordered_map<std::string, Employee&> list;
    public:
        EmployeeManager(EmployeeManager const&) = delete;
        void operator=(const&) = delete;
        void place(const std::string &id, Employee &emp){
            list[id] = emp;
        }
};

class Employee
{
    public:
        virtual void doSomething() = 0;
};

class Writer : public Employee
{
    private: 
        std::string name_;
    public:
        Writer(std::string name) : name_(name) {};
        void doSomething() { };
};

老实说,我从来没有尝试过单身模式,而且因为没有先前的经验而我不想直接使用它,我宁愿先在我的宠物项目中使用它。

3 个答案:

答案 0 :(得分:2)

  

这是使用全局静态变量或单例的好方法吗?

不,因为您可能会遇到另一个问题:static initialization order fiasco。有办法解决它 - 但使用静态变量的函数 - 看起来就像单身。

...但为什么你需要全局变量(甚至在命名空间中)或单身?在第一个示例中,如果您namespace ListA代替struct ListA而不是删除namespace{,那将完全没问题。然后你有:

int main() {
    ListA list;
    list.init();
    list.place("b1", new B("b1"));
    list.place("a1", new A("a1"));
}

它看起来很好。

然后你的单例aproach,再次 - 不需要它 - 在你的main函数中创建类型EmployeeManager的变量,如果你需要在其他类中使用它,然后通过引用或指针传递它。 / p>

答案 1 :(得分:1)

我不确定你是否已经知道,但是你需要记住 Singleton 确实是 一个具有延迟初始化的全局变量

延迟初始化是一种工具,用于解决在您真正想要使用它时始终初始化对象的问题 - 无论是对于某些实际程序函数还是初始化另一个依赖对象。这样做是为了将初始化延迟到使用对象的第一个时刻。

静态对象只是在需要创建第一个的时刻进行初始化 - 但是当这个时刻确实存在时,是未定义的,至少在C ++中是这样。

您可以使用静态初始化替换延迟初始化,但必须确保初始化以定义的顺序发生。

在命名空间内定义变量只不过是全局声明变量。命名空间是开放的,命名空间内的规则与命名空间外的规则相同,除了符号解析。

强制执行有序初始化的目的是创建一个全局变量,其中包含所有依赖的全局对象,形式为struct,将所有对象都包含为字段(静态领域!)。请注意,只有在作为该结构的字段的对象之间才能确保初始化的确切顺序,而不是在它们与任何其他全局对象之间。

答案 2 :(得分:0)

您的问题可以在没有任何代码的情况下得到解答,因为过去很多人都回答过。单身人士很糟糕,因为你的代码将依赖于一个类及其实现。你想要的是拥有独立的单位,他们不了解他们交谈的接口的实现。值/引用的传播应该(实际上必须对大型可维护系统进行)通过从包含对象传递到其子节点,观察者/事件系统或事件/消息总线的引用。许多框架都使用这些方法中的两种......我强烈建议坚持使用最佳实践。