将静态容器用于基类和派生类

时间:2016-09-05 15:10:14

标签: c++ oop c++11 static stl

我正在尝试构建一个[base+derived]类的对象容器(std::map是准确的),它将是一个静态变量,嵌入在Base类中,以便每个从其衍生出来的其他对象,或者从它的子类中最多只能列出一个列表。此外,如果没有创建对象,我仍然可以通过其他友好(静态)函数访问该列表。

这是代码 -

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

class A{

    std::string id;
    static std::unordered_map<std::string, A*> list;

  public:

    A(std::string _i): id(_i){}
    static void init(){
        // doSomeProcessing or let's just call clear() for now
        list.clear();
    }
    static void place(std::string _n, A* a){
        list[_n] = a;
    }
    virtual void doSomething(){std::cout << "DoSomethingBase\n";}
};

std::unordered_map<std::string, A*> A::list = {};

class B : public A{

    std::string name;

  public:

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


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

代码编译成功但是,我有两件事情,我可能做错了,一个主要问题和另一个小问题。

专业 - 将指针指向std::map的值?我确定我还没有完全掌握它们,但我努力学习。我知道他们不会被删除,但我应该怎么做?在析构函数中?或让程序结束.....(不好,对吧?)

次要 - 使用静态成员?我从民众那里听说静态变量因某些未知原因而变坏,除非必要,否则不应使用。甚至从某些早期版本的cpp中删除了它们。是对的吗?但是我会选择......在main()中创建它。

我可能在这里做的任何其他错误。

修改 - 我提供了一个示例方案,以便通过以下示例清除我的意图。

  

让我们说我有一个employee课程和一些更具体的工作,例如editorwriterdeveloper仍然来自员工。但是,也可能存在基类employee的实例,因为可能还有一些员工尚未进行分类,但很快就会出现。

让我们说这是一项要求,尽管我愿意接受改变。我使用静态std::map,因此只需要一个实例,它在所有基类+派生类中是统一的。我对单身人士模式并不熟悉,但我将来会很高兴地学习它。

5 个答案:

答案 0 :(得分:4)

专业:使用托管指针。以下是基于unique_ptr的代码的修改版本:

class A{
    // ...
    static std::unordered_map<std::string, std::unique_ptr<A>> list;

    // ...
    template< class T, class... Args >
    static
    // enable only A-based objects for T via the return type
    typename std::enable_if<std::is_base_of<A, T>::value>::type
    place(std::string _n, Args&&... args)
    {
        list.emplace(_n, std::make_unique<T>(std::forward<Args>(args)...));
    }
    // ...
};

std::unordered_map<std::string, std::unique_ptr<A>> A::list = {};

class B : public A{

    // ...
    // corrected a warning here: bases should be constructed before members
    B(std::string _n):A(_n), name(_n){}
    // ...
};

int main() {
    A::init();
    A::place<B>("b1", "b1");
    A::place<A>("a1", "a1");
        // maybe, the second argument is now redundant
        // you can remove it and call in place
        // std::make_unique<T>(_n, std::forward<Args>(args)...)
    return 0;
}

我修改了place以使其分配 - 使用make_unique即C ++ 14,您可以使用new T{std::forward<Args>(args)...}代替。新的place函数是跟make_unique原型的variadic template

正如Adam Martin的评论中所述,enable_if可用于将类型T限制为继承A的对象。

由Jarod42编辑,您应该使用forward references来尊重调用place时传递的值类别。

我使用emplace代替operator[]来避免不必要的复制或移动操作。

如果您需要复制指针,另一个选择是使用shared_ptr。实施将类似。

答案 1 :(得分:3)

拥有静态成员可能是一件好事。特别是,如果静态成员服务于该类的某些内部管理,则主要使用静态成员。最突出的例子是参考计数器。

所以你必须回答的主要问题是:你真正想做什么?

如果您的对象的调用者必须手动调用place等,我会认为这是糟糕的设计。这些管理功能只能在你的类内部完成(特别是构造函数和析构函数)。

主要观点:只需使用std::shared_ptr<A>即可。这样可以自动跟踪删除对象。

修改

根据您的编辑,您使用静态地图的方法看起来很合理。 然而,你一定要改变责任。使用工厂模式:

每个类都有一个静态方法create或类似的方法。您的构造函数应为privateA::place("x", new A("x"))表示您随时可以随意拨打new A("x")。这违反了你创造单身人士的目标。

相反,创建实例应始终为A::create("x")B::create("x")。每个类也可以有自己的映射(因为您永远不需要访问基类&#39; map)。

然而,正确实施单例可能是一项复杂的任务。你可以考虑避免单身人士(除非你有充分的理由)。

相反,为什么不简单地正确覆盖operator==operator!=

答案 2 :(得分:3)

至于删除地图中的指针,这是其中一个问题,只有那些问它的人知道答案。

在程序终止的情况下,这是一个明显的非问题。内存分析工具可能会抱怨内存未被释放。如果这是一个问题,那么解决它。在退出程序之前,请明确删除地图中的指针。

在所有其他用例中,没有人能告诉您答案。当你需要删除指针时,你必须自己弄明白。在大多数用例中,删除指针作为从地图中删除它的一部分将是正确的答案。

但是,当然,如果指针的对象仍然被使用,在某种程度上,在它的键被从地图中删除之后,在完成之前不能删除该对象。仅仅因为从地图中删除了对象的密钥并不意味着有一条禁止对象被使用的法律。

只有你知道在对象被移除之后你将要对象做什么,如果有的话,那么你再来决定。复杂的大型应用程序很少使用普通指针,而是使用智能指针(std::shared_ptr),以便让C ++自己在任何地方不再使用对象时可以解决,并且可以被销毁。

就静态班级成员而言,这纯属意见。 static类成员是C ++的一部分。这本身并不会使它们变得好,也不会使它们变坏。有很好的方法可以使用静态类成员,并且有不好的方法。对于许多其他C ++功能也是如此。

答案 3 :(得分:3)

在设计方面,我不确定您的课程要完成什么/为什么B来自A。听起来你应该有三个班级: EmployeeManager(A的一部分,列表管理,使用Singleton模式实现),Employee(A的其他部分,即虚拟方法)和Editor,{{ 1}}等(B现在)。您的Writer课程目前似乎有多重责任,这违反了单一责任。在这种情况下,我们将列表管理和员工界面分成两个类。

示例实施:

A

如果您希望员工自动插入经理,您可以在员工类中引用经理,并在其构造函数中添加位置。

就指针而言,您可以使用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() { }; }; 或使用引用代替。

答案 4 :(得分:3)

  

将指针作为std :: map的值? ......他们在这里没有被删除,但我该怎么办呢?在析构函数中?

如果指向的对象应该由容器拥有,那么是的,应该在析构函数中销毁拥有的对象。但是,您应始终使用智能指针来管理拥有的对象。使用智能指针,隐式析构函数将做正确的事。

如果指向的对象不归容器所有,那么它们应该由其他东西拥有。

  

使用静态成员?

静态成员是全局变量。您是否应该或不应该使用全局变量已在其他地方广泛讨论过,并且超出了我的答案范围。

  甚至从某些早期版本的cpp中删除了它们。

从来没有一个已发布的C ++标准版本,其中静态成员并不存在。