继承POD类型的大括号初始化

13
#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类型,因此std::is_pod<der_pod_t>::value的结果是正确的。


你的编译器不符合规范。g++ 4.8.2生成的二进制文件显示der_pod_t不是POD类型。 - BЈовић
@BЈовић说gcc版本为4.8.2 20131212(Red Hat 4.8.2-7)(GCC)是一个POD。 - name
显然是一个bug。但gcc版本4.8.2(Ubuntu 4.8.2-19ubuntu1)却没有报错。 - BЈовић
3
正如Praetorian所说,它是一个POD类型,但它不是聚合类型。 - dyp
4个回答

18

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

来自§8.5.1 [dcl.init.aggr]

  

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

     

2 当以初始化器列表的形式初始化聚合体时,如8.5.4所指定,初始化器列表的元素按照递增的下标或成员顺序作为聚合体成员的初始值。每个成员从相应的初始化子句进行复制初始化。...

然而,der_pod_t不是聚合体,因为它有一个基类。它是一个POD,并且不适用于相同的列表初始化规则。现在,当编译器看到非空花括号初始化列表时,它首先会搜索接受initializer_list的构造函数。如果找不到,则尝试匹配类的其他构造函数。由于der_pod_t没有带单个int参数的构造函数,因此出现错误。


我刚刚阅读了标准,正如你所说的那样。POD不能继承(§9.0.10 §9.0.7第二个项目符号)。谢谢。 - name
@name 你引用的是哪个标准版本?我在N3936中没有看到任何说明der_pod_t不是POD的内容。der_pod_t是标准布局,因为它的基类也是标准布局。它同时满足平凡类标准布局类的要求,因此它是一个POD。 - Praetorian
这是一个 POD。我误解了标准。谢谢。 - name
1
der_pod_t 自 C++17 起是一个聚合体。 - M.M

11

从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上可以工作(之前的版本不确定),但在带有“/std:c++17”标志的VS17(v 15.9.4)上无法工作,因此请注意您的编译器支持/标志。

相关变更建议在此处


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 类型。在这种情况下,类型转换是没有任何构造函数的,它只是假装内存格式正确。如果您尝试在没有指针的情况下重新解释转换,则编译器将尝试基于原始对象构造一个新对象。

请参见 this answer 以获得更好的解释。

不幸的是,似乎没有一个漂亮的一行代码可以解决这个问题。声明的适当宏似乎是必要的。


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 仅表示:

具有一个或多个默认构造函数,所有这些构造函数都是平凡的或已删除,并且其中至少有一个未被删除。

它不禁止自定义构造函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接