C++:可以一次添加多个对象的多态容器

Posted

技术标签:

【中文标题】C++:可以一次添加多个对象的多态容器【英文标题】:C++: Polymorphic container which can add several objects at once 【发布时间】:2013-01-09 12:54:48 【问题描述】:

尝试重构一些基础架构代码我决定使用继承来实现多个类之间的多态行为。下一步是将它们放入容器中,遍历它们并 DoSomething();

我目前的容器构造很笨拙。即使我对容器使用这个elegant 解决方案,构造仍然是一团糟。

vector<unique_ptr<Parent>> vec;

vec.push_back(unique_ptr<Parent>(new ChildA(var1, var2)));
vec.push_back(unique_ptr<Parent>(new ChildB(var3, var4)));
vec.push_back(unique_ptr<Parent>(new ChildC(var5, var6)));

ChildA、ChildB 和 ChildC 彼此都是多态的。 Var1 ... var6 可能来自不同的类型,并且是构建子对象所必需的。

我想在几个地方创建这个向量以供使用。问题是孩子的数量可能会有所不同,因此孩子需要构建的数据。尝试走 stdarg.h 的路径失败,因为不支持:

if     !defined(_WIN32)
#error ERROR: Only Win32 target supported!
#endif

您将如何实现构造此数组的工厂方法?

更新,想要的解决方案:

vec = CreatePolyVec(var1, var2, typeInfo1, var3, var4, typeInfo2, var5, var6 typeInfo3);
vec = CreatePolyVec(var1, var2, typeInfo1, var3, var4, typeInfo2);

第 1 行将创建与上面相同的数组。 第 2 行将重用相同的代码来创建一个相似的向量,但对象更少。 Typeinfo 保存创建对象所需的信息。

这是一个基本的解决方案,更高级的解决方案将在编译时强制执行参数列表。例如以下对函数的调用没有意义:

vec = CreatePolyVec(var1, var2,typeinfo1, var3, var4, typeinfo2, var5);

没有足够的参数来创建最后一个子对象。

【问题讨论】:

你的编译器支持 C++11 可变参数模板吗? 您能否提供所需解决方案的示例用例(以代码形式)? 您的应用程序是 WinRT 还是 .NET 应用程序?可能是这个原因? 我来自 Windows 领域,我用 C++ 编写。 正在阅读您的更新...您可能应该查看如何进行“完美转发”。 【参考方案1】:

这里有两个问题,1.如何决定每个孩子会是什么类型,2.如何创建多个孩子。

决定创建哪个孩子

这可以在编译时或运行时完成。要在编译时执行此操作,您需要模板。

template<class Child, class Arg1, class Arg2>
vector<unique_ptr<Parent>> CreateVec(Arg1&& arg1, Arg2&& arg2)

    vector<unique_ptr<Parent>> result;
    result.push_back(unique_ptr<Child>(
        new Child(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2))));
    return result;

如下调用CreateVec&lt;MyChild&gt;(myArg1, myArg2)

如果您需要在运行时决定,您可以使用由运行时变量索引的工厂函数映射。或者您可以使用指向工厂对象的指针作为运行时变量。

创建多个孩子

在这里,您可以在链式函数和可变参数模板之间进行选择。

链式函数

这本质上就是 iostreams 所做的。让创建向​​量并添加单个子项的函数返回一个对象,该对象允许您添加第二个子项,并返回自身允许您继续。

这里的问题是你希望函数返回向量,所以它不能同时返回另一个对象。您可以使用转换运算符来获取向量,也可以使用显式函数,但可能最简单的方法是先创建向量,然后使用函数添加子项。

class AddChildren

    vector<unique_ptr<Parent>>& m_vector;
public:
    explicit AddChildren(vector<unique_ptr<Parent>>& theVector)
        : m_vector(theVector) 

    template<class Child, class Arg1, class Arg2>
    AddChildren& add(Arg1&& arg1, Arg2&& arg2)
    
        m_vector.push_back(unique_ptr<Child>(
            new Child(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2))));
        return *this;
    
;

如下使用:

vector<unique_ptr<Parent>> myvector;
AddChildren(myvector)
    .add<ChildA>(var1, var2)
    .add<ChildB>(var3, var4)
    .add<ChildC>(var5, var6);

如果您使用运行时方法来选择类型,您可以使用 operator() 并使其看起来像这样:

vector<unique_ptr<Parent>> myvector;
AddChildren(myvector)
    (childAType, var1, var2)(childBType, var3, var4)(childCType, var5, var6);

(这也可以通过使用每个子类型的特定类型选择器类型的虚拟对象作为参数来在编译时选择类型。)

使用可变参数模板

使用可变参数模板一次剥离三个参数,并添加一个子对象。

void addChildren(vector<unique_ptr<Parent>>& theVector)


template<class FirstChild, class FirstArg1, class FirstArg2, class... Rest>
void addChildren(vector<unique_ptr<Parent>>& theVector,
    FirstChild childtype, FirstArg1&& arg1, FirstArg2&& arg2, Rest&&... rest)

    addChild(theVector, childtype,
        std::forward<Arg1>(arg1), std::forward<Arg2>(arg2));
    addChildren(theVector, std::forward<Rest>(rest)...);


template<class... Args>
vector<unique_ptr<Parent>> CreateVec(Args&&... args)

    vector<unique_ptr<Parent>> result;
    addChildren(result, std::forward<Args>(args)...);
    return result;


我在这里假设存在一个函数addChild,它可以在给定其类型(作为参数)及其参数的情况下添加一个子项。

这样做的主要问题是 VS2012 没有可变参数模板。有两种方法可以模拟可变参数模板。 1. 编写一个函数,该函数采用您可能需要的最大参数数量,并将它们中的大多数默认为某种已知类型,您可以将其视为“不存在”。 2. 写出你认为需要的尽可能多的重载。

如果您知道您永远不需要超过十个子对象,那么第二个选项实际上是完全可行的——您只需要编写一次它们并且可能少于 150 行代码。或者,您可以使用 Boost.Preprocessor 来生成函数,但这是一个全新的复杂程度。

【讨论】:

随着 2012 年 11 月的编译器更新,VS 2012 确实具有可变参数模板。 兔子洞有多深。很好的答案!【参考方案2】:

随着 2012 年 11 月的编译器更新,VS 2012 支持可变参数模板,因此以下应该可以工作(这不是您所追求的语法,但我认为它非常接近):

struct VectorCreator

  explicit VectorCreator(std::vector<Parent> &vec)
    : vec_(vec)
  

  template <typename Type, typename... ArgType>
  VectorCreator& create(ArgType&&... arg)
  
    vec_.push_back(std::unique_ptr<Parent>(new Type(std::forward<ArgType>(arg)...));
    return *this;
  

private:
  std::vector<Parent> &vec_;
;

这样使用:

std::vector<Parent> vec;
VectorCreator(vec).create<ChildA>(var1, var2).create<ChildB>(var3, var4).create<ChildC>(var5, var6);

【讨论】:

我觉得应该是std::forward&lt;ArgType&amp;&amp;&gt; @ymett 我不这么认为。如果参数最初来自 sometype&amp;,您希望将其转发为 sometype&amp;。注意std::forward&lt;T&gt;的返回类型是T&amp;&amp; 你是对的,但事实证明std::forward&lt;T&amp;&amp;&gt; 具有完全相同的效果(T&amp; &amp;&amp; -> T&amp;)。

以上是关于C++:可以一次添加多个对象的多态容器的主要内容,如果未能解决你的问题,请参考以下文章

从 《Accelerated C++》源码学习句柄类

C++之STL

异常安全 C++ 共享指针

多态(继承)和值类型

C++多态性与虚函数

C++在多态子类中添加虚方法