Bar*_*ett 48 c++ initialization list-initialization c++17
似乎普遍认为,大括号初始化应优先于其他形式的初始化,但是由于将C++ 17 扩展引入聚合初始化,似乎存在意外转换的风险.请考虑以下代码:
struct B { int i; };
struct D : B { char j; };
struct E : B { float k; };
void f( const D& d )
{
E e1 = d; // error C2440: 'initializing': cannot convert from 'D' to 'E'
E e2( d ); // error C2440: 'initializing': cannot convert from 'D' to 'E'
E e3{ d }; // OK in C++17 ???
}
struct F
{
F( D d ) : e{ d } {} // OK in C++17 ???
E e;
};
Run Code Online (Sandbox Code Playgroud)
在上面的代码中struct D
,struct E
代表两个完全不相关的类型.所以我很惊讶,从C++ 17开始,如果使用大括号(聚合)初始化,你可以在没有任何警告的情况下从一种类型"转换"到另一种类型.
您会建议避免这些类型的意外转换?或者我错过了什么?
PS:上面的代码在Clang,GCC和最新的VC++中进行了测试 - 它们都是一样的.
更新:回应尼科尔的回答.考虑一个更实际的例子:
struct point { int x; int y; };
struct circle : point { int r; };
struct rectangle : point { int sx; int sy; };
void move( point& p );
void f( circle c )
{
move( c ); // OK, makes sense
rectangle r1( c ); // Error, as it should be
rectangle r2{ c }; // OK ???
}
Run Code Online (Sandbox Code Playgroud)
我可以理解你可以查看a circle
作为一个point
,因为circle
有point
基类,但你可以默默地从一个圆转换为一个矩形的想法,这对我来说是一个问题.
更新2:因为我对课程名称的选择不好似乎使某些人的问题蒙上阴影.
struct shape { int x; int y; };
struct circle : shape { int r; };
struct rectangle : shape { int sx; int sy; };
void move( shape& p );
void f( circle c )
{
move( c ); // OK, makes sense
rectangle r1( c ); // Error, as it should be
rectangle r2{ c }; // OK ???
}
Run Code Online (Sandbox Code Playgroud)
Nic*_*las 37
struct D和struct E代表两种完全不相关的类型.
但它们并非"完全不相关"的类型.它们都具有相同的基类类型.这意味着每个都D
可以隐式转换为a B
.因此每一个D
都是 B
.这样做与调用的操作E e{d};
没有区别E e{b};
.
您无法关闭对基类的隐式转换.
如果这真的困扰你,唯一的解决方案是通过提供一个将值转发给成员的适当构造函数来防止聚合初始化.
至于这是否会使聚合初始化更加危险,我不这么认为.您可以使用以下结构重现上述情况:
struct B { int i; };
struct D { B b; char j; operator B() {return b;} };
struct E { B b; float k; };
Run Code Online (Sandbox Code Playgroud)
所以这种性质总是存在的可能性.我不认为使用隐式基类转换会使它"更糟糕".
更深层次的问题是用户为什么尝试E
使用a D
来初始化a .
你可以默默地从一个圆圈转换成一个矩形的想法,这对我来说是一个问题.
如果你这样做,你会遇到同样的问题:
struct rectangle
{
rectangle(point p);
int sx; int sy;
point p;
};
Run Code Online (Sandbox Code Playgroud)
你不仅可以执行rectangle r{c};
,但是rectangle r(c)
.
您的问题是您正在使用继承错误.你说约之间的关系的事情circle
,rectangle
和point
你没有意思.因此,编译器允许您执行您不想做的事情.
如果您使用了包含而不是继承,这不会是一个问题:
struct point { int x; int y; };
struct circle { point center; int r; };
struct rectangle { point top_left; int sx; int sy; };
void move( point& p );
void f( circle c )
{
move( c ); // Error, as it should, since a circle is not a point.
rectangle r1( c ); // Error, as it should be
rectangle r2{ c }; // Error, as it should be.
}
Run Code Online (Sandbox Code Playgroud)
无论circle
是总是一个point
,或者是从来没有一个point
.你试图让它成为point
有时而不是其他人.这在逻辑上是不连贯的.如果您创建逻辑上不连贯的类型,那么您可以编写逻辑上不连贯的代码.
你可以默默地从一个圆圈转换成一个矩形的想法,这对我来说是一个问题.
这提出了一个重点.严格来说,转换看起来像这样:
circle cr = ...
rectangle rect = cr;
Run Code Online (Sandbox Code Playgroud)
那是不合理的.当你这样做时rectangle rect = {cr};
,你没有进行转换.您正在显式调用列表初始化,这对于聚合通常会引发聚合初始化.
现在,列表初始化当然可以执行转换.但是仅仅给出D d = {e};
,人们不应该指望这意味着你正在进行从a e
到a的转换D
.你是D
用一个列表初始化一个类型的对象e
.如果E
可以转换为可以执行转换D
,但是如果非转换列表初始化表单也可以工作,则此初始化仍然有效.
所以说这个功能可以circle
转换成是不正确的rectangle
.
Bar*_*rry 29
这在C++ 17中并不新鲜.聚合初始化总是允许你放弃成员(这将从空的初始化列表C++ 11初始化):
struct X {
int i, j;
};
X x{42}; // ok in C++11
Run Code Online (Sandbox Code Playgroud)
刚才有更多种类的东西可以省去,因为可以包含更多种类的东西.
gcc和clang至少会通过-Wmissing-field-initializers
(它的一部分-Wextra
)提供警告,表明缺少某些东西.如果这是一个非常大的问题,只需在启用该警告的情况下进行编译(并且可能会升级为错误):
<source>: In function 'void f(const D&)':
<source>:9:11: warning: missing initializer for member 'E::k' [-Wmissing-field-initializers]
E e3{ d }; // OK in C++17 ???
^
<source>: In constructor 'F::F(D)':
<source>:14:19: warning: missing initializer for member 'E::k' [-Wmissing-field-initializers]
F( D d ) : e{ d } {} // OK in C++17 ???
^
Run Code Online (Sandbox Code Playgroud)
更直接的只是向这些类型添加构造函数,以便它们不再是聚合.你不具备使用集合初始化,毕竟.
归档时间: |
|
查看次数: |
3893 次 |
最近记录: |