我知道C ++ 11的统一初始化解决了该语言在语法上的歧义,但是在许多Bjarne Stroustrup的演示文稿中(尤其是在GoingNative 2012演讲期间的演示文稿中),他的示例现在在构造对象时都主要使用此语法。 />
现在建议在所有情况下都使用统一初始化吗?就编码风格和一般用法而言,此新功能的一般方法是什么?不使用它的原因有哪些?

请注意,我的想法主要是将对象构造作为用例,但是如果要考虑其他场景,请告诉我。 />

评论

这可能是在Programmers.se上讨论得更好的主题。它似乎倾向于良好的主观方面。

@NicolBolas:另一方面,您的出色答案可能是c ++-faq标记的很好候选者。我认为我们之前对此没有解释。

我的意见:这比旧式“ Type x = y”可读性差,因此请勿将其用于基本类型,因为这不会带来任何好处。实际上,基本类型没有构造函数(因此没有错误选择的风险),并且您可以(并且应该)让编译器在进行不使用强制转换的缩小转换时发出警告(即使MSVC发出警告)。 (我的代表不足以发布答案)。

#1 楼

编码风格最终是主观的,从中获得实质性性能收益的可能性很小。但是,我要说的是,您从统一初始化的自由使用中受益: />
为什么需要两次键入vec3?有什么意义吗?编译器非常清楚该函数返回什么。我为什么不能只说“用这些值调用返回的构造函数并返回它”?通过统一的初始化,我可以:

vec3 GetValue()
{
  return vec3(x, y, z);
}


一切正常。

函数参数甚至更好。考虑以下问题:

vec3 GetValue()
{
  return {x, y, z};
}


无需键入类型名即可工作,因为std::string知道如何从const char*隐式构建自身。那很棒。但是,如果该字符串来自RapidXML,那该怎么办。或Lua字符串。也就是说,假设我实际上知道字符串的长度。如果我只是传递std::string,则采用const char*const char*构造函数将必须采用字符串的长度。但是要使用它,我必须这样做:DoSomething(std::string(strValue, strLen))。为什么在那里有多余的类型名?编译器知道类型是什么。就像使用auto一样,我们可以避免使用额外的类型名:

void DoSomething(const std::string &str);

DoSomething("A string.");


它可以正常工作。没有类型名称,没有大惊小怪,什么也没有。编译器可以完成工作,代码更短,每个人都很高兴。也就是说,很明显正在发生什么,谁在做什么。在一定程度上说是对的。了解统一的基于初始化的代码需要查看函数原型。这就是为什么有人说您永远不要通过非const引用传递参数的相同原因:这样您就可以在调用站点看到是否正在修改值。

但是DoSomething(std::string(strValue, strLen))可以说相同;要了解从auto得到的收益,需要查看auto v = GetSomething();的定义。但这并没有阻止GetSomething一旦获得访问权就被鲁near弃用。就个人而言,我认为一旦习惯了就可以了。尤其是具有良好的IDE。

永远都无法获得最令人讨厌的解析结果。

这里有一些代码。测验:什么是auto?如果回答“变量”,那是错误的。它实际上是一个函数的原型,该函数以返回foo的函数作为参数作为参数,而Bar函数的返回值是一个整数。

这被称为C ++的“最复杂的解析”,因为它绝对对人类毫无意义。但是C ++的规则令人遗憾地要求:如果可以将其解释为函数原型,那么它将是。问题是foo;那可能是两件事之一。它可能是名为Bar()的类型,这意味着它正在创建一个临时文件。或者它可以是不带参数并返回Bar的函数。

统一初始化不能解释为函数原型: Bar始终创建一个临时文件。 Bar{}总是创建一个变量。

在很多情况下,您都想使用int foo{...},但由于C ++的解析规则而不能使用。有了Typename(),就不会有歧义。您不能使用统一的初始化功能来初始化较大的值。您可以使用老式的初始化来执行此操作,而不是使用统一的初始化。否则,关于初始化列表的类型将存在很多模棱两可的情况。

当然,有些人可能认为这样的代码不值得编译。我个人碰巧同意;缩小范围非常危险,并且可能导致不愉快的行为。最好在编译器阶段尽早发现这些问题。至少,缩小范围表明某人对代码的思考不是太认真。

请注意,如果您的警告级别很高,则编译器通常会警告您这种情况。因此,实际上,所有这些操作都是将警告变为强制错误。有人可能会说您仍然应该这样做;)

还有另一个原因不这样做:

它可以创建带有一百个默认构造项目的Typename{}。或者它可以创建一个vector<int>,其中包含1个值为vector<int>的商品。两者在理论上都是可能的。

实际上是后者。

为什么?初始化程序列表使用与统一初始化相同的语法。因此,必须有一些规则来解释在模棱两可的情况下该怎么做。规则很简单:如果编译器可以将初始化器列表构造函数与大括号初始化的列表一起使用,则它将使用。由于100具有使用vector<int>的初始化程序列表构造函数,因此{100}可能是有效的initializer_list<int>,因此它必须是有效的。

请注意,如果这是initializer_list<int>不能转换为整数的内容,则不会发生。 initializer_list不适合该()类型的初始化列表构造函数,因此编译器可以从其他构造函数中自由选择。

评论


+1钉了它。我正在删除我的答案,因为您的答案将更详细地说明所有相同的观点。

– R. Martinho Fernandes
2012年2月6日,下午3:19

最后一点是为什么我真的想要std :: vector v {100,std :: reserve_tag};。与std :: resize_tag类似。当前需要两个步骤来保留向量空间。

– Xeo
2012年2月6日在3:49

@NicolBolas-两点:我认为麻烦的解析问题是foo(),而不是Bar()。换句话说,如果您执行了int foo(10),是否会遇到相同的问题?其次,不使用它的另一个原因似乎更多是工程过度的问题,但是如果我们使用{}构造所有对象,但是一天之后,我为初始化列表添加了构造函数,该怎么办?现在,我所有的构造语句都变成了初始化列表语句。在重构方面似乎非常脆弱。对此有何评论?

–void.pointer
2012年2月6日14:25

@RobertDailey:“如果您使用了int foo(10),是否会遇到相同的问题?” No. 10是整数文字,而整数文字绝不能是类型名。令人烦恼的解析来自Bar()可能是类型名或临时值的事实。这就是造成编译器歧义的原因。

–尼科尔·波拉斯(Nicol Bolas)
2012年2月6日在16:09

令人不快的行为-有一个新的标准术语要记住:>

–sehe
2012-12-26 23:07:

#2 楼

我不同意Nicol Bolas的答案部分“最小化冗余类型名”。因为代码只能编写一次并可以多次读取,所以我们应该尽量减少读取和理解代码所花费的时间,而不是编写代码所花费的时间。尝试仅最小化键入就是在尝试优化错误的东西。

请参见以下代码:

第一次可能不会立即理解return语句,因为到他到达那一行时,他将已经忘记了return类型。现在,他必须回滚到函数签名或使用一些IDE功能才能查看返回类型并完全理解return语句。第一次了解真正构造的代码:并添加以下重载:

vec3 GetValue()
{
  <lots and lots of code here>
  ...
  return {x, y, z};
}


如果CoolStringType碰巧有一个构造函数,该构造函数使用const char *和size_t(就像std :: string一样),则调用DoSomething({strValue,strLen})会导致模棱两可的错误。

我对实际问题的回答:样式构造函数语法。

我的推理是这样的:
如果两个语句没有相同的意图,则它们应该看起来不一样。对象初始化有两种概念:
1)将所有这些项都放入要初始化的对象中。
2)使用我作为指导提供的这些参数构造该对象。
br />
#1概念的使用示例:

void DoSomething(const std::string &str);
...
const char* strValue = ...;
size_t strLen = ...;

DoSomething({strValue, strLen});


#2概念的使用示例:

void DoSomething(const CoolStringType& str);


我认为新标准允许人们像这样初始化Stairs是不好的事情:

...因为混淆了构造函数的含义。这样的初始化看起来像概念1,但事实并非如此。即使看起来像一样,也不会在对象s中倒入三个不同的步长值。而且,更重要的是,如果已经发布了如上所述的Stairs的库实现,并且程序员一直在使用它,然后,如果库实现者后来向Stairs添加了initializer_list构造函数,则所有使用Stairs并进行统一初始化的代码语法即将中断。

我认为C ++社区应就统一初始化的使用方式达成共识,即统一使用所有初始化,或者像我强烈建议的那样,将这两个概念分开初始化,从而向程序员的代码读者阐明程序员的意图。替换旧语法,以及为什么不能对所有初始化使用大括号表示法:

说,制作副本的首选语法是:

struct Collection
{
    int first;
    char second;
    double third;
};

Collection c {1, '2', 3.0};
std::array<int, 3> a {{ 1, 2, 3 }};
std::map<int, char> m { {1, '1'}, {2, '2'}, {3, '3'} };


现在您认为应该将所有初始化替换为n大括号语法,以便您可以(并且代码看起来)更加一致。但是,如果类型T为集合,则使用花括号的语法将不起作用:

class Stairs
{
    std::vector<float> stepHeights;

public:
    Stairs(float initHeight, int numSteps, float stepHeight)
    {
        float height = initHeight;

        for (int i = 0; i < numSteps; ++i)
        {
            stepHeights.push_back(height);
            height += stepHeight;
        }
    }
};

Stairs s (2.5, 10, 0.5);


评论


如果您有“ <这里有很多代码”,则无论采用哪种语法,都很难理解您的代码。

–kevin cline
2012年12月18日在17:21

除了IMO,IDE的职责还在于告知您返回的类型(例如,悬停)。当然,如果您不使用IDE,那么您就要负担自己的负担了:)

– abergmeier
2012-12-22 11:44

@TommiT我同意您所说的某些内容。但是,本着自动与显式类型声明辩论的精神,我主张一种平衡:在模板元编程情况下,无论哪种类型通常都很明显,统一的初始化程序会花费很多时间。它将避免重复您的织补-> decltype(....)用于简单的oneline功能模板(让我哭泣)。

–sehe
2012-12-26 23:50



“但是如果类型T是一个聚合,则使用花括号的语法将不起作用:”请注意,这是标准中报告的缺陷,而不是有意的预期行为。

–尼科尔·波拉斯(Nicol Bolas)
13 Mar 10 '13 at 0:40

“现在,他必须回滚到功能签名”,如果必须滚动,则说明您的功能太大。

–Miles Rout
2014年11月3日,下午3:41

#3 楼

如果在各自的类变量merely copy their parameters中的构造函数in exactly the same order中声明了它们,则使用统一初始化最终会比调用构造函数更快(但也可以完全相同)。这不会改变必须始终声明构造函数的事实。

评论


您为什么说它可以更快?

– jbcoe
16-09-26在21:08

这是不正确的。不需要声明构造函数:struct X {int i; }; int main(){X x {42}; }。同样正确的是,统一初始化可能比值初始化更快。

– Tim
19年7月19日在20:42