【问题标题】:Declare inherited templatized objects and push them back into vector - elegant way?声明继承的模板化对象并将它们推回向量 - 优雅的方式?
【发布时间】:2013-06-19 19:00:22
【问题描述】:

我正在尝试声明许多从Base 继承的模板化Derived<T> 对象,并将它们推回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&amp; 传递给Derived&lt;T&gt;::Derived&lt;T&gt;(...) 构造函数的情况下自动执行此操作?

自动化是指避免对象名称的重复。我想用我所有的Derived&lt;T&gt; 对象填充std::vector,而不必手动列出它们。

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

【问题讨论】:

  • 你能用std::vector&lt;Derived&lt;bool&gt;&gt;代替吗?
  • “自动化”是什么意思?
  • @justin: T 在我的应用程序中并不总是bool。对不起,这个例子没有显示出来。 @tmpearce:我的意思是避免重复对象名称。我想用我所有的 Derived&lt;T&gt; 对象填充一个向量,而不必手动列出它们。
  • @CaptainObvlious 完成。
  • 你想完成什么?看来您可能对boost::any 感兴趣。

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


【解决方案1】:

因此,生命周期将是您面临的问题之一,而在您的程序中如何使用这种模式是对解决方案的另一个影响。

因此,假设您的 Derived 实例不属于向量,那么您需要确保它们的寿命更长。共有三种基本方法,在某些情况下可以组合使用。

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

第二种是使用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】:

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

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

    如果您确实需要在构造函数中修改字符串,您可以更改参数以通过右值引用或按值传递字符串。

    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 来管理对象的生命周期。

    【讨论】:

    • 为什么在你的最后一个例子中没有make_shared
    【解决方案3】:

    听起来您要使用工厂模式类型的实现,使用一个通用的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 应该有助于内存管理。您可以使用静态容器或制作一个对象来容纳所有容器,这没什么大不了的。

    【讨论】:

      【解决方案4】:

      感谢您的回答,都投了赞成票。 最后我决定创建一个处理所有权和创建的“助手”类。

      来自我的 GitHub SSVUtilsJson repo:

      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 repo)

      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 */ }
      

      【讨论】:

        猜你喜欢
        • 2021-07-29
        • 1970-01-01
        • 2013-04-01
        • 1970-01-01
        • 2011-07-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多