【问题标题】:Abstract class as std::initializer_list object作为 std::initializer_list 对象的抽象类
【发布时间】:2015-02-22 15:56:18
【问题描述】:

为了获得更简洁的语法,我想使用 std::initializer_list 将对象列表发送到构造函数。然而,这些对象是抽象的,这会导致一个问题:在 VS 2013 中,它丢失了 vfptr 引用,给出了“R6025:纯虚函数调用”运行时错误,并且在 g++ 中它抱怨它“无法分配抽象类型'base'的对象”在编译期间。我推测编译器正在尝试复制对象(这是不可取的——它们可能很大),但只成功复制了基类,因此出现了错误。我的问题是:是否有一种解决方案(1)避免复制对象并且(2)不是非常冗长,否定“更简洁的语法”优势?下面的代码说明了我的问题:

#include <cstdio>
#include <initializer_list>

struct base{
    virtual void foo() const = 0;
};

struct derived : public base{
    int i;
    derived(int i) : i(i) {}
    void foo() const{
        printf("bar %i", i);
    }
};

void foo_everything(const std::initializer_list<base> &list){
    for (auto i = list.begin(), iend = list.end(); i != iend; i++) i->foo();
}

int main(void){

    // Works fine
    derived d(0);
    base * base_ptr = &d;
    base_ptr->foo();    

    // Does not work fine
    foo_everything({ derived(1), derived(2), derived(3) });
}

注意,在模板中使用 base& 会出错,因为 std::initializer_list 会尝试“[form a] pointer to reference type base&”,并且在使用 base* 时,然后获取每个的地址派生类确实有效,它通过获取临时地址来实现,因此不安全(g++ 抱怨)。如果我在方法调用之外声明派生类(我的临时解决方案),后者确实有效,但它仍然比我希望的更冗长。

【问题讨论】:

  • 一个std::initializer_list&lt;base&gt; 商店,嗯,bases。使用可变参数模板。
  • 或者使用指针(这在你的情况下会很棘手)。
  • 广告const std::initializer_list&lt;base&gt; &amp;initializer_list&lt;T&gt; 类具有指针语义,因此无需通过引用传递它们。
  • 潜在的问题是,这是试图将derivedarray 传递到derived 的大小未知的某些上下文。将一组指针传递给derived 是可能的,即使这些指针包含临时地址:initializer_list 本身的生命周期已经存在问题,因此恕我直言,这并不是真正危险得多。
  • 我不确定,但如果标准允许std::initializer_list&lt;abstract_class&gt;,我会感到非常惊讶。毕竟,initializer_list&lt;T&gt; 是数组的轻量级包装器,抽象类型的数组是不可能的。

标签: c++ c++11


【解决方案1】:

使用initializer_list&lt;base *&gt; 有点骇人听闻的方法:

template<class... Ts>
void foo_everything(Ts&&... args){
    std::initializer_list<base *> list = {&args...};
    for(auto i : list) i->foo();
}

然后从调用中删除大括号:

foo_everything(derived(1), derived(2), derived(3));

如果你真的不需要转换为base *并执行虚拟调用,而只想对传入的每个对象调用foo(),那么我们可以使用通常的pack-expansion-inside-an-初始化列表技巧:

template<class... Ts>
void foo_everything(Ts&&... args){
    using expander = int[];
    (void) expander { 0, ((void) std::forward<Ts>(args).foo(), 0)...};
}

【讨论】:

  • 使用模板for_everything(如我的回答)在这里似乎更合适。
  • @Walter 无法处理多个派生类的列表。
  • 这很好。但是,我觉得使用单个方法而不是两个方法更简洁,例如,将模板化方法中的调用替换为定义std::initializer_list&lt;base *&gt; list{&amp;args...} 和循环for(auto i : list) i-&gt;foo()。如果您认为这与您的答案具有相同的语义,并且想附加它,请随意。否则,请将其添加为我的问题的编辑(无论哪种方式,学分和正确答案都是你的)。
【解决方案2】:

如果你想要语法

foo_everything({ derived(1), derived(2), derived(3) });

要工作,您必须使用模板。例如,

template<typename T>
void foo_everything(std::initializer_list<T> list)
{
  for(const auto&x:list) x.foo();
}

您可以添加一些 SFINAE 魔法来确保 T 是从基础派生的:

template<typename T>
typename std::enable_if<is_base_of<base,T>::value>::type
foo_everything(std::initializer_list<T> list)
{
  for(const auto&x:list) x.foo();
}

如果您想在一次调用foo_everything 时使用不同的派生类,那么您不能使用您喜欢的语法

foo_everything({ derived(1), derived(2), derived(3) });

。 (句号)这仅仅是因为std::initializer_list 包装了一个数组,并且一个数组必须具有相同类型的对象。所以在这个严格意义上,答案很简单:做不到。

【讨论】:

  • 作为 T.C.指出,这不允许使用不同的派生类,这是我使用基类的目标。这在问题中并不完全清楚,对此我深表歉意。
猜你喜欢
  • 2012-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-30
  • 1970-01-01
  • 2014-04-29
  • 1970-01-01
  • 2021-06-10
相关资源
最近更新 更多