可以使用 C++ 聚合初始化来构造实现接口的类的实例吗?

Posted

技术标签:

【中文标题】可以使用 C++ 聚合初始化来构造实现接口的类的实例吗?【英文标题】:Can C++ aggregate initialization be used to construct an instance of a class which implements an interface? 【发布时间】:2019-05-09 18:01:10 【问题描述】:

我希望有人能给我详细说明为什么以下代码无法编译,如果可能的话,解决方法。

我有一个名为 Foo 的现有结构,以及使用初始化列表创建 Foo 实例的代码。此代码编译并工作:

struct Foo 
    int id1;
    int id2;
;

int main()

    Foo f(1,2);

    return f.id1;

我希望 Foo 实现一个接口:

struct Interface 
    // All pure virtual methods, but this won't compile even if empty
;

struct Foo : public Interface
    int id1;
    int id2;
;

int main()

    Foo f(1,2);

    return f.id1;

此代码不再编译,出现

的错误
cannot convert argument 1 from 'initializer list' to 'const _Ty &'

(错误会根据您的确切编译器而变化。)

我找到了与聚合初始化相关的这部分标准:

[dcl.init.aggr]/1 聚合是一个数组或一个类(第 12 条) 1.1 没有用户提供的、显式的或继承的构造函数(15.1), 1.2 没有私有或受保护的非静态数据成员(第 14 条), 1.3 没有虚拟功能(13.3),和 1.4 没有虚拟、私有或受保护的基类 (13.1)。

虽然我实际上不确定聚合初始化是否是这里发生的事情。有人可以解释正在发生的错误,如果可能的话,提供我可以对界面进行的更改吗?我有几个需要这个接口的现有结构,以及许多使用这种初始化形式的现有代码,我想尽可能少地重写它。谢谢!

【问题讨论】:

【参考方案1】:

你需要初始化基类,即使它是空的:

Foo f(,1,2);

see it live on godbolt

在您所指部分的标准中,我们可以在[dcl.init.aggr]p4.2 中看到一个示例:

struct base1  int b1, b2 = 42; ;
struct base2 
  base2() 
   b3 = 42;
 
 int b3;
;

struct derived : base1, base2 
 int d;
;

derived d11, 2, , 4;
derived d2, , 4;

用 1 初始化 d1.b1,用 2 初始化 d1.b2,用 42 初始化 d1.b3,用 4 初始化 d1.d, d2.b1 为 0,d2.b2 为 42,d2.b3 为 42,d2.d 为 4。 —end 例子]

另见[dcl.init.aggr]p2,它解释了聚合的元素是什么:

聚合的元素是:

-对于数组,数组元素按下标递增顺序,或 -对于一个类,按声明顺序的直接基类,然后是直接非静态数据成员([class.mem]) 匿名工会的成员,按声明顺序。

和[dcl.init.aggr]p3 说:

当聚合被 [dcl.init.list] 中指定的初始化列表初始化时,初始化列表的元素被视为聚合元素的初始化。 ...

请注意,自 before C++17 an aggregate was not allowed to have a base class 以来,答案假定 C++17 或更高版本。

【讨论】:

+1 可以用struct Interface ; struct Foo : public Interface int id1; int id2; ; int main() Foo f = .id1 = 1, .id2 = 2 ; return f.id1; 躲避子弹并声称它适用于C++20 吗? :-) ` @TedLyngmo 我不知道我是否会推荐,gcc 和 clang 都支持 C99 指定初始化程序作为 C99 扩展,但 clang reports their C++20 support for designated initializers as partial @NicolBolas 我认为普遍认为 C++ 标签意味着流行的 C++,并且自 C++14 does not allow this 以来 OP 使用的引用是 C++17 或更高版本,但我绝对可以添加注释。跨度> @ShafikYaghmour 我也不会推荐它,但它是(/将可能成为)有效的 C++20,不是它(除了未初始化的基数)?【参考方案2】:

@ShafikYaghmour 解释了为什么当Interface 为空时,聚合初始化不能像以前那样进行。

但如果Interface 具有虚函数,正如问题中所建议的,来自Interface 的派生类将不是聚合。因此,实现Interface 并将数据成员保存为Foo 的类必须实现构造函数。我看到的最简单的方法(根据数据成员的“琐碎性”,在速度方面可能不是最有效的)是这样的:

struct Interface 
   // All pure virtual methods, but this won't compile even if empty
   virtual void bar() =0;
   ;

struct Foo_data //change the name of the aggregate
  int id1;
  int id2;
  ;

struct Foo
  :Interface  //Foo has virtual function => Foo is not an aggregate
  ,Foo_data
  
  Foo() =default;
  Foo(Foo_data data):Foo_data(std::move(data))//a constructor must be provided
  void bar() override 
  ;

int main()
  Foo f(1,2);
  return f.id1;
  

【讨论】:

以上是关于可以使用 C++ 聚合初始化来构造实现接口的类的实例吗?的主要内容,如果未能解决你的问题,请参考以下文章

C++中的类和对象

用C++设计一个不能被继承的类(转)

C++只能实例化1个对象的类

匿名内部类

C++ 类的复制控制

c++ 当我创建结构数组时,如何使用结构数组内的类的参数调用构造函数?