大括号初始化继承的pod

时间:2014-05-22 13:30:46

标签: c++ c++11 pod

#include <iostream>
#include <type_traits>


struct base_pod_t {
    unsigned x;
};

struct der_pod_t : public base_pod_t { };

int main()
{
    std::cout << "base_pod_t is POD: " << std::is_pod<base_pod_t>::value << std::endl;
    std::cout << "der_pod_t  is POD: " << std::is_pod<der_pod_t>::value << std::endl;
    base_pod_t b1 = {};     // OK
    base_pod_t b2 = {3};    // OK

    der_pod_t p1 = {};      // OK
//    der_pod_t p2 = {4};   // ERROR!
}

最后一行导致错误。如何用值来初始化der_pod_t


似乎即使它是POD它试图使用构造函数?


修改 由于@Praetorian和@dyb建议它是POD result of std::is_pod<der_pod_t>::value is correct.

4 个答案:

答案 0 :(得分:14)

base_pod_t是一个聚合,您正在执行的初始化是聚合初始化。

来自§8.5.1[dcl.init.aggr]

  

1 聚合是一个数组或类(第9条),没有用户提供的构造函数(12.1),没有私有或受保护的非静态数据成员(第11条),没有基类(第10条),也没有虚函数(10.3)。

     

2 当聚合由初始化列表初始化时,如8.5.4中所述,初始化列表的元素被视为聚合成员的初始化者,增加下标或成员订购。每个成员都从相应的 initializer-clause 进行复制初始化。 ...

但是,der_pod_t不是聚合,因为它有一个基类。这是一个POD,列表初始化的相同规则不适用。现在,当编译器看到一个非空的 braced-init-list 时,它将首先搜索一个带有initializer_list的构造函数。如果没有找到它,则尝试匹配该类的其他构造函数。由于der_pod_t没有构造函数将单个int作为参数,因此会发生错误。

答案 1 :(得分:4)

从CPP 17开始,这是允许的,但要稍作改动,您需要为每个基类在初始化器列表中添加其他{}。请注意,在下面的示例中,{1,2}如何包含在“ {}”中并初始化i,j,而“ 3”则初始化派生的k。

struct base_pod
{
    int i, j;

};

struct der_pod : public base_pod
{
    int k;
};

der_pod dp{ {1 , 2}, 3 };

这适用于GCC 7.3.0版(不确定较早的版本),但在VS17(v 15.9.4)和带有“ / std:c ++ 17”标志的VS17上失败,因此注意您的编译器支持/标志。

relevant change proposal is here

答案 2 :(得分:0)

我今天一直在处理这个问题,并找到了解决方案,但我不能强调这个解决方案有多危险(见下文为什么危险)。

我的特殊问题是我只想用我自己的一些方法扩展库结构。我想保持POD与基本完全相同的布局,因为我想使用以base为参数的函数。

解决方案是这样的:

#include <iostream>
using namespace std;

struct BASE {
  int x, y;
};

struct FOO: BASE {
  void Foo() { x = y = 1; }
};

int main() {
  // const declaration
  const BASE a = { 0, 1 };
  const FOO &b = *reinterpret_cast<const FOO *> (&a);

  // non-const declaration
  BASE _a = { 0, 3 };
  FOO &c = *reinterpret_cast<FOO *> (&_a);

  cout << "base: " << a.x << ", " << a.y << endl;
  cout << "foo 1: " << b.x << ", " << b.y << endl;
  cout << "foo 2: " << c.x << ", " << c.y << endl;

  return 0;
}

但请注意,这只能起作用,因为BASE和FOO之间的数据布局是相同的。也只是因为我使用指针强制转换为FOO类型。在这种情况下,类型转换在没有任何构造函数的情况下完成,它只是假装内存格式正确。如果你尝试在没有指针的情况下reinterpret_cast,编译器将尝试基于原始构造一个新对象。

有关更好的说明,请参阅this answer

不幸的是,对于这一点来说,似乎并不是一个很好的单线。适当的声明宏似乎是有序的。

答案 3 :(得分:0)

尝试一下;

struct A {
  float data;
  A() = default;
  A(float d) : data{d} {}
};

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

测试:

  A aa{1}; // OK
  B bb{1}; // OK
  std::cout << std::is_pod<A>::value << std::endl; // output 1
  std::cout << std::is_pod<B>::value << std::endl; // output 1

输出将显示A和B均为POD。

https://en.cppreference.com/w/cpp/named_req/TrivialType仅说:

  

具有一个或多个默认构造函数,所有这些构造函数都是微不足道的或已删除,并且至少有一个未删除。

它不允许自定义构造函数。