可以使用 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++ 聚合初始化来构造实现接口的类的实例吗?的主要内容,如果未能解决你的问题,请参考以下文章