声明继承的模板化对象并将它们推回矢量 - 优雅的方式?

时间:2013-06-22 21:56:27

标签: c++ templates inheritance c++11 vector

我正在尝试声明许多从Derived<T>继承的模板Base对象,然后将它们推回std::vector<Base*>

struct Base { ... };
template<typename T> struct Derived : Base { /* ctor(const string&) */ ... }

Derived<bool> online{"online"};
Derived<bool> official{"official"};
Derived<bool> noRotation{"no_rotation"};
Derived<bool> noBackground{"no_background"};
Derived<int> noSound{"no_sound"};
Derived<string> noMusic{"no_music"};
Derived<bool> blackAndWhite{"black_and_white"};

vector<Base*> configValues{&online, 
                           &official, 
                           &noRotation, 
                           &noBackground, 
                           &noSound, 
                           &noMusic, 
                           &blackAndWhite};

正如您所看到的,代码非常糟糕。有没有办法在vector构造函数中将const&作为Derived<T>::Derived<T>(...)传递时自动执行此操作?

通过自动化我的意思是避免重复对象的名称。我想用std::vectorDerived<T>填充所有{{1}}个对象,而不必手动列出所有对象。

(Macros接受了,但希望有更好的解决方案)

4 个答案:

答案 0 :(得分:1)

这里似乎有两个不同的问题......

第一个问题似乎是您不希望将常量std::string传递给Derived<T>的构造函数。我能想到的唯一原因是在构造Derived<T>对象时是否需要修改字符串。如果您不需要修改字符串,我建议您使用const引用它,就像您目前所做的那样。

如果您执行需要修改构造函数中的字符串,您可以更改参数以通过rvalue引用或值传递字符串。

Derived(std::string&& str) { /*...*/ } // Pass by r-value reference
Derived(std::string str) { /*...*/ }   // Pass by value

两者都允许你在构造过程中修改字符串。


关于你评论中的第二个问题......

要以评论中描述的方式填充向量,您可以使用统一初始化。唯一需要注意的是,您需要为添加到矢量中的对象使用动态存储持续时间。

std::vector<Base*> configValues{
    {
    new Derived<bool>{"online"},
    new Derived<bool>{"official"},
    new Derived<bool>{"no_rotation"},
    new Derived<bool>{"no_background"},
    new Derived<int>{"no_sound"},
    new Derived<std::string>{"no_music"},
    new Derived<bool>{"black_and_white"}
    }
};

我不确定你的目标是什么,所以你决定如何管理这些对象的生命周期取决于你。我强烈建议使用智能指针来管理其生命周期所有权。例如,如果您需要共享所有权,则可以使用std::shared_ptr,例如以下示例。

std::vector<std::shared_ptr<Base>> configValues{
    {
    std::shared_ptr<Base>(new Derived<bool>{"online"}),
    std::shared_ptr<Base>(new Derived<bool>{"official"}),
    std::shared_ptr<Base>(new Derived<bool>{"no_rotation"}),
    std::shared_ptr<Base>(new Derived<bool>{"no_background"}),
    std::shared_ptr<Base>(new Derived<int>{"no_sound"}),
    std::shared_ptr<Base>(new Derived<std::string>{"no_music"}),
    std::shared_ptr<Base>(new Derived<bool>{"black_and_white"})
    }
};

由于std::initializer_list要求对象可以复制,因此无法使用std::unique_ptr来管理对象的生命周期。

答案 1 :(得分:1)

因此,生命周期将是您面临的问题之一,以及如何在您的计划中使用此模式是对解决方案的另一个影响。

假设您的Derived&lt;&gt;实例不归矢量所有,那么您需要确保它们比它更长。有三种基本方法,在某些情况下可以合并。

第一个:创建一个存储和填充向量的类。如果你有相同的Derived&lt;&gt;类型或参数重复,这至少可以“重复删除”您的常见结构。然后你可以给这些成员函数返回或填充向量。

第二种是使用std :: tuple。如果有许多参数列表变体并且您希望一个实例存储所有这些Derived实例,同时有办法创建常见例程(如填充向量),则元组可能很有用:

typedef std::tuple<
    Derived<bool>,
    Derived<bool>,
    Derived<bool>,
    Derived<bool>,
    Derived<bool>,
    Derived<int>,
    Derived<std::string>,
    Derived<bool>
> atuple;

atuple t{
    "online",
    "official",
    "no_rotation",
    "no_background",
    "no_sound",
    "no_music",
    "black_and_white"
};
const size_t size(std::tuple_size<atuple>::value);
/* or you could use std::array because the size is constant. */
std::vector<Base*>configValues;
configValues.reserve(size);
push(t,configValues);

push()看起来像是:

template<std::size_t I = 0, typename V, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
push(std::tuple<Tp...>& t, V&)
{ }

template<std::size_t I = 0, typename V, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
push(std::tuple<Tp...>& t, V& vec)
{
 vec.push_back(&std::get<I>(t));
 push<I + 1, V, Tp...>(t, vec);
}

(借鉴iterate over tuple)。

如果您在程序的多个部分中没有遇到此问题,那么这些解决方案对您来说就不会那么有用了。

第三种 - 使用数组:

 std::array<Derived<bool>,5> a{{{"online"},{"official"},{"no_rotation"},{"no_background"},{"black_and_white"}}};
 Derived<int> noSound{"no_sound"};
 Derived<string> noMusic{"no_music"};
 vector<Base*> configValues{&noSound,&noMusic};
 for (Derived<bool>& b:a) configValues.push_back(&b); // original order not retained

答案 2 :(得分:1)

听起来你正在采用工厂模式类型的实现,使用通用的container来添加所有派生对象。

这是一个让你入门的工作示例(ideone):

#include <string>
#include <vector>
#include <memory>
#include <algorithm>
#include <iostream>

struct Base
{
    typedef std::shared_ptr<Base> SharedPtr;
    virtual ~Base(){}
    virtual void print(){}
    template<class T>
    static SharedPtr makeDerived(const std::string& name);

    static std::vector<SharedPtr> objs;
};
std::vector<Base::SharedPtr> Base::objs;
template<class T>
struct Derived : public Base
{
    Derived(const std::string& name):name(name){}
    void print(){std::cout<<name<<std::endl;}
    std::string name;
};

template<class T>
Base::SharedPtr Base::makeDerived(const std::string& name)
{
    SharedPtr p = std::make_shared<Derived<T> >(Derived<T>(name));
    objs.push_back(p);
    return p;
}

int main()
{
    Base::makeDerived<bool>("online");
    Base::makeDerived<int>("users");
    std::for_each(Base::objs.begin(),Base::objs.end(),
                  [](Base::SharedPtr p){p->print();}   );
}

shared_ptr应该有助于内存管理。您可以使用静态容器或制作一个容器来容纳所有容器,但不会产生很大的影响。

答案 3 :(得分:0)

感谢您的回答,全部投票。 最后,我决定创建一个处理所有权和创造的“助手”课程。

来自我的GitHub SSVUtilsJson回购:

class Manager
{
    private:
        ssvu::MemoryManager<Base> memoryManager; 
        // MemoryManager internally has a vector<Base*> of owned pointers

    public:
        template<typename T> Derived<T>& create() { return memoryManager.create<Derived<T>>(); }
        // MemoryManager::create<T> creates and stores a 'new T*' and returns a reference to it
};

用法(来自我的GitHub SSVOpenHexagon回购邮件)

Manager lvm;

auto& online                    (lvm.create<bool>());
auto& official                  (lvm.create<string>());
auto& noRotation                (lvm.create<int>());
auto& noBackground              (lvm.create<double>());
auto& noSound                   (lvm.create<char>());
auto& noMusic                   (lvm.create<void>());

for(Base* b : lvm.getItems()) { /* do something */ }