【问题标题】:Why does having a base class disqualify a class from being aggregate?为什么有一个基类会取消一个类的聚合资格?
【发布时间】:2013-11-07 21:13:50
【问题描述】:

拥有一个聚合公共基类(或什至多个聚合公共基类)会使一个类失去聚合类的优良属性是什么?

“聚合基类”的定义来自 http://en.cppreference.com/w/cpp/language/aggregate_initialization http://en.wikipedia.org/wiki/C++_classes#Aggregate_classes


聚合类的优点:

  • 在不定义构造函数的情况下,可以通过传入用大括号括起来的值列表来初始化聚合类型,以初始化其成员(或基类,如果它们允许的话)。
  • 聚合类型被认为是“简单的”(PODs 的泛化),并且可以用作 constexprs 的文字类型。

来自http://en.cppreference.com/w/cpp/language/aggregate_initialization#Example的初始化示例:

#include <string>
#include <array>
struct S {
  int x;
  struct Foo {
    int i;
    int j;
    int a[3];
  } b;
};

int main()
{
  S s1 = { 1, { 2, 3, {4, 5, 6} } };
  S s2 = { 1, 2, 3, 4, 5, 6}; // same, but with brace elision
}

另请参阅: What are Aggregates and PODs and how/why are they special?

【问题讨论】:

  • 我猜这是因为您无法使用初始化列表初始化此类聚合的成员。
  • 您特别关注哪些“好属性”?列出它们,举例说明它们应该如何处理空基类和非空基类以及虚拟继承,当你完成这些工作时,你就会得到答案。或者一个更好的问题。
  • 所以现在看来​​你所说的所有它真正获得的好处是你必须少写一个构造函数,一个可能只有一行的构造函数
  • 聚合类型可用作文字类型 (en.cppreference.com/w/cpp/concept/LiteralType),而后者又可用于 constexpr 参数和返回值 (en.cppreference.com/w/cpp/language/constexpr)。 constexpr 函数可用于生成编译时常量。但就这个问题而言,我想要它真的很重要吗?
  • @leewangzhong 我喜欢 constexpr 的东西,虽然我想我已经使用它而不考虑它,请记住 constexprs 在 c++14 中将更加通用

标签: c++ c++11


【解决方案1】:

根据http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3308.pdf

尽管标准的意图很明确,但类型 B 不是文字类型:

struct A {}; struct B : A {};

这是因为它的构造函数在被 odr 使用之前没有被隐式定义,并且在那之前它没有 constexpr 构造函数。

不过,老实说,我不确定这意味着什么。

【讨论】:

    【解决方案2】:

    这是来自 c++11 标准的聚合的定义,这就是我能给出的所有内容,而无需猜测委员会做出此决定时的想法。

    1 聚合是一个数组或类(第 9 条),没有用户提供的构造函数(12.1),没有用于非静态数据成员的大括号或等号初始化器(9.2),没有私有或受保护的非静态数据成员(第 11 条),无基类(第 10 条),无虚函数(10.3)。

    粗体字表示聚合没有基类。

    至于另一个答案提出的继承问题,您可以使用继承进行统一初始化。注意:A 仍然是一个聚合。

    struct A {                                                                         
        int val_A;                                                                                                                        
    };                                                                                 
    
    struct B : public A {                                                              
        int val_B;                                                                     
        B(int a, int b) : A{a}, val_B(b) {}                                            
    };                                                                                 
    int main() {                                                                       
        B b {2,3};                                                                     
        return 0;                                                                      
    }  
    

    您只需给 B 一个构造函数,IMO 标准可以很容易地选择它作为默认值。聚合可能被保留,因为它们在以前的标准中,但事实是使用 c++11 特性,你甚至不需要它们。事实上,一个问题是 std::array 需要双括号,因为它没有初始化列表构造函数,我认为这个问题在 c++14 中得到了解决。

    我想补充一点,考虑到统一初始化和初始化列表的新特性,我看不到聚合给类增加了多少。

    【讨论】:

    • 他在问为什么有一个基类(它本身就是一个聚合)会导致一个类不再是一个聚合
    • @Praetorian 标准不必做出这个决定,它没有说明原因。我知道我没有回答完整的问题,但我还能说什么
    • 那么你的答案不妨改写为因为标准是这样写的。我并不是说这一定是错误的答案,只是你的答案,正如目前所写的那样,完全绕过了这个问题。
    • @Praetorian 很公平,我改变了它
    • -1:对不起,我知道你试过了,但这根本不能回答提出的问题。也许这是无法回答的,但我宁愿看不到答案。
    【解决方案3】:

    由于 C++17 类具有公共的非虚拟基类,因此可以聚合。

    struct Base1 {
     int a, b;
    };
    struct Base2 {
     int c;
    };
    
    struct Foo : Base1, Base2 {
        int d, e;
    };
    

    struct Foo 的对象可以聚合初始化。以下初始化是等价的:

    Foo foo {Base1{1, 2}, Base2{3}, 4, 5};
    
    Foo foo {{1, 2}, {3}, 4, 5};
    
    Foo foo {1, 2, 3, 4, 5};
    

    【讨论】:

    • 我绑定到 C++14 并最终使用聚合而不是继承,因为它在我的用例中没有发挥作用:struct Foo { Base1 base1; Base2 base2; int d, e; }; 我可以使用此处所述的三个初始化变体(当然,最新变体中缺少大括号警告)。
    • 附带说明,在我之前的评论之后,如果我想访问 a、b 和 c,我必须 foo.base1.a; foo.base1.b; foo.base2.c;。为了使它更有用(或处理遗留代码),我添加了别名,即struct Foo { Base1 base1; Base2 base2; int d, e; int&amp; a{base1.a}; int&amp; b{base1.b}; int&amp; c{base2.c}; };,然后我可以像继承版本一样使用foo.a; foo.b; foo.c;。这当然会导致更高的维护工作量,但在我的某些用例中可能是一个可行的解决方案。
    【解决方案4】:

    你将如何初始化基类?

    Derived d = {{1, 2}, 3, 4};
    

    或者

    Derived d = {1, 2, 3, 4};
    

    Derived d = {3, 4};
    

    允许吗?

    为了避免所有这些,没有基类。

    【讨论】:

    • 除了我很确定派生类可以使用统一的初始化语法,最终看起来一样
    • @aaronman:我很确定它不能,除非您编写一个构造函数,在这种情况下,它控制参数的顺序以及传递给基类的数量。
    • 假设基类被移动为第一个成员。您将如何初始化聚合类的第一个成员?我会说这个问题的答案是一样的。
    • 另外,我很确定第二个通常可以代替第一个(int a[2][2] = {1,2,3,4} 是合法的,AFAIK)。
    • @leewangzhong:不在旧标准中。不确定 C++11,但我对此表示怀疑。
    猜你喜欢
    • 1970-01-01
    • 2020-03-16
    • 1970-01-01
    • 2016-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-13
    • 2015-01-03
    相关资源
    最近更新 更多