jjzjj

c++ - 聚合初始化的 C++17 扩展是否使大括号初始化变得危险?

coder 2023-05-01 原文

似乎普遍认为brace initialization should be preferred超过其他形式的初始化,但是自从引入 C++17 extension to aggregate initialization似乎存在意外转换的风险。考虑以下代码:

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;
};

在上面的代码中,struct Dstruct E代表了两个完全不相关的类型。所以令我惊讶的是,从 C++17 开始,如果您使用大括号(聚合)初始化,您可以在没有任何警告的情况下从一种类型“转换”为另一种类型。

您有什么建议来避免这些类型的意外转化?还是我错过了什么?

PS:上面的代码在 Clang、GCC 和最新的 VC++ 中测试过——它们都是一样的。

更新:回应 Nicol 的回答。考虑一个更实际的例子:

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 ???
}

我可以理解您可以将 circle 视为 point,因为 circlepoint 为基础类,但是您可以默默地将圆形转换为矩形的想法对我来说是个问题。

更新 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 ???
}

最佳答案

struct D and struct E represent two completely unrelated types.

但它们不是“完全不相关”的类型。它们都具有相同的基类类型。这意味着每个 D 都可以隐式转换为 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; };

所以这种性质的东西总是有可能的。我不认为使用隐式基类转换会使它变得“更糟”。

一个更深层次的问题是,为什么用户尝试用 D 来初始化 E

the idea that you can silently convert from a circle to a rectangle, that to me is a problem.

如果你这样做,你会遇到同样的问题:

struct rectangle
{
  rectangle(point p);

  int sx; int sy;
  point p;
};

你不仅可以执行rectangle r{c};,还可以执行rectangle r(c)

您的问题是您错误地使用了继承。你说的是 circlerectanglepoint 之间的关系,你不是这个意思。因此,编译器可以让你做你不想做的事情。

如果您使用包含而不是继承,这将不是问题:

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.
}

circle 要么是 always 一个 point,要么它是 never 一个 point。您有时会尝试使其成为 point 而不是其他人。这在逻辑上是不连贯的。如果你创建逻辑不连贯的类型,那么你可以编写逻辑不连贯的代码。


the idea that you can silently convert from a circle to a rectangle, that to me is a problem.

这带来了一个重要的观点。严格来说,转换如下所示:

circle cr = ...
rectangle rect = cr;

这是不正确的。当您执行rectangle rect = {cr}; 时,您没有 进行转换。您正在显式调用列表初始化,这对于聚合通常会引发聚合初始化。

现在,列表初始化当然可以执行转换。但是仅考虑 D d = {e};,人们不应该期望这意味着您正在执行从 eD 的转换.您正在使用 eD 类型的对象进行列表初始化。如果 E 可转换为 D 则可以执行转换,但如果非转换列表初始化形式也可以工作,则此初始化仍然有效。

所以说这个特性让circle可以转换成rectangle是不正确的。

关于c++ - 聚合初始化的 C++17 扩展是否使大括号初始化变得危险?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50254467/

有关c++ - 聚合初始化的 C++17 扩展是否使大括号初始化变得危险?的更多相关文章

  1. ruby-on-rails - 未初始化的常量 Psych::Syck (NameError) - 2

    在我的gem中,我需要yaml并且在我的本地计算机上运行良好。但是在将我的gem推送到ruby​​gems.org之后,当我尝试使用我的gem时,我收到一条错误消息=>"uninitializedconstantPsych::Syck(NameError)"谁能帮我解决这个问题?附言RubyVersion=>ruby1.9.2,GemVersion=>1.6.2,Bundlerversion=>1.0.15 最佳答案 经过几个小时的研究,我发现=>“YAML使用未维护的Syck库,而Psych使用现代的LibYAML”因此,为了解决

  2. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  3. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  4. ruby-on-rails - 未在 Ruby 中初始化的对象 - 2

    我在Rails工作并有以下类(class):classPlayer当我运行时bundleexecrailsconsole然后尝试:a=Player.new("me",5.0,"UCLA")我回来了:=>#我不知道为什么Player对象不会在这里初始化。关于可能导致此问题的操作/解释的任何建议?谢谢,马里奥格 最佳答案 havenoideawhythePlayerobjectwouldn'tbeinitializedhere它没有初始化很简单,因为你还没有初始化它!您已经覆盖了ActiveRecord::Base初始化方法,但您没有调

  5. ruby-on-rails - ActionController::RoutingError: 未初始化常量 Api::V1::ApiController - 2

    我有用于控制用户任务的Rails5API项目,我有以下错误,但并非总是针对相同的Controller和路由。ActionController::RoutingError:uninitializedconstantApi::V1::ApiController我向您描述了一些我的项目,以更详细地解释错误。应用结构路线scopemodule:'api'donamespace:v1do#=>Loginroutesscopemodule:'login'domatch'login',to:'sessions#login',as:'login',via::postend#=>Teamroutessc

  6. ruby - 这两个 Ruby 类初始化定义有什么区别? - 2

    我正在阅读一本关于Ruby的书,作者在编写类初始化定义时使用的形式与他在本书前几节中使用的形式略有不同。它看起来像这样:classTicketattr_accessor:venue,:datedefinitialize(venue,date)self.venue=venueself.date=dateendend在本书的前几节中,它的定义如下:classTicketattr_accessor:venue,:datedefinitialize(venue,date)@venue=venue@date=dateendend在第一个示例中使用setter方法与在第二个示例中使用实例变量之间是

  7. c - mkmf 在编译 C 扩展时忽略子文件夹中的文件 - 2

    我想这样组织C源代码:+/||___+ext||||___+native_extension||||___+lib||||||___(Sourcefilesarekeptinhere-maycontainsub-folders)||||___native_extension.c||___native_extension.h||___extconf.rb||___+lib||||___(Rubysourcecode)||___Rakefile我无法使此设置与mkmf一起正常工作。native_extension/lib中的文件(包含在native_extension.c中)将被完全忽略。

  8. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  9. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

  10. ruby - 带括号和 splat 运算符的并行赋值 - 2

    我明白了:x,(y,z)=1,*[2,3]x#=>1y#=>2z#=>nil我想知道为什么z的值为nil。 最佳答案 x,(y,z)=1,*[2,3]右侧的splat*是内联扩展的,所以它等同于:x,(y,z)=1,2,3左边带括号的列表被视为嵌套赋值,所以它等价于:x=1y,z=23被丢弃,而z被分配给nil。 关于ruby-带括号和splat运算符的并行赋值,我们在StackOverflow上找到一个类似的问题: https://stackoverflow

随机推荐