为什么我应该更喜欢“明确types的初始值设定项”成语,而不是明确给出types

我最近从Scott Meyers购买了新的有效的现代C ++,现在阅读。 但是我遇到一件事情,那就是完全的错误。

斯科特在第5项中说,使用auto是一件好事。 它可以节省打字的时间,在大多数情况下可以提供正确的打字types,并且可以避免types不匹配。 我完全理解这一点,并认为auto也是一件好事。

但是,斯科特在第6项中说,每一枚硬币都有两面。 同样,在auto推断完全错误的types(例如代理对象)时,可能会出现这种情况。

你可能已经知道这个例子:

 class Widget; std::vector<bool> features(Widget w); Widget w; bool priority = features(w)[5]; // this is fine auto priority = features(w)[5]; // this result in priority being a proxy // to a temporary object, which will result // in undefined behavior on usage after that // line 

到现在为止还挺好。

但斯科特的解决scheme,就是所谓的“明确types的初始化成语”。 想法是,在初始化程序上使用static_cast,如下所示:

 auto priority = static_cast<bool>(features(w)[5]); 

但是,这不仅会导致更多的打字,而且还会明确说明应该推断的types。 你基本上失去了明确给定types的auto优势。

谁能告诉我,为什么使用这个成语是有好处的?


首先要澄清事情,我的问题旨在为什么我应该写:

 auto priority = static_cast<bool>(features(w)[5]); 

代替:

 bool priority = features(w)[5]; 

@Sergey提供了一个关于这个话题的一个很好的文章的链接,这部分地回答了我的问题。

指南:考虑声明局部variablesauto x = type {expr}; 当你想显式地提交一个types。 显示代码是明确请求转换的,这是自我logging,它确保variables将被初始化,并且不会允许意外的隐式缩小转换。 只有在确实需要明确缩小时,才可以使用()而不是{}。

这基本上给我一个相关的问题。 我应该select哪四种select?

 bool priority = features(w)[5]; auto priority = static_cast<bool>(features(w)[5]); auto priority = bool(features(w)[5]); auto priority = bool{features(w)[5]}; 

头号仍然是我的最爱。 这不像其他三个那么明确。

关于保证初始化的观点并不真正成立,因为我无论如何都在声明variables的时候不能把它们初始化。 而关于缩小的另一个观点在快速testing中效果不佳(参见http://ideone.com/GXvIIr )。

遵循C ++标准:

[dcl.init]初始化程序[dcl.init]

  1. 在窗体中发生的初始化

     T x = a; 

    以及parameter passing,函数返回,抛出exception(15.1),处理exception(15.3)和聚合成员初始化(8.5.1)被称为复制初始化

我可以想一下书中给出的例子:

 auto x = features(w)[5]; 

作为代表任何forms的复制初始化与自动/模板types(一般推导types ),就像:

 template <typename A> void foo(A x) {} foo(features(w)[5]); 

以及:

 auto bar() { return features(w)[5]; } 

以及:

 auto lambda = [] (auto x) {}; lambda(features(w)[5]); 

所以问题是,我们不能总是“将T从static_cast<T>到赋值的左侧”

相反,在上面的任何一个例子中,我们都需要明确地指定所需的types,而不是让编译器自行推断,如果后者可能导致未定义的行为

分别给我的例子,将是:

 /*1*/ foo(static_cast<bool>(features(w)[5])); /*2*/ return static_cast<bool>(features(w)[5]); /*3*/ lambda(static_cast<bool>(features(w)[5])); 

因此,使用static_cast<T>是强制所需types的优雅方式,也可以通过显式的构造器调用来expression:

 foo(bool{features(w)[5]}); 

总之,我不认为这本书说:

每当你想强制一个variables的types,使用auto x = static_cast<T>(y); 而不是T x{y};

对我来说,这听起来更像是一个警告:

使用autotypes推断很酷,但如果使用不当,可能会导致不确定的行为。

作为涉及types扣除情况的解决scheme,提出了以下build议:

如果编译器的常规types推导机制不是您想要的,请使用static_cast<T>(y)


UPDATE

并回答您更新的问题, 下面哪个初始化应该更喜欢

 bool priority = features(w)[5]; auto priority = static_cast<bool>(features(w)[5]); auto priority = bool(features(w)[5]); auto priority = bool{features(w)[5]}; 

情况1

首先,想象std::vector<bool>::reference 不能隐式转换为bool

 struct BoolReference { explicit operator bool() { /*...*/ } }; 

现在, bool priority = features(w)[5];不会编译 ,因为它不是一个明确的布尔上下文。 其他人将正常工作(只要operator bool()是可访问的)。

情景2

其次,我们假设std::vector<bool>::reference是以旧的方式实现的,虽然转换运算符不是explicit ,但是它会返回int

 struct BoolReference { operator int() { /*...*/ } }; 

签名的改变closuresauto priority = bool{features(w)[5]}; 初始化,因为使用{}可防止缩小 (将int转换为bool )。

情景3

第三,如果我们说的不是关于bool的话,而是关于一些用户定义的types,我们惊讶地声明了explicit构造方法:

 struct MyBool { explicit MyBool(bool b) {} }; 

令人惊讶的是, MyBool priority = features(w)[5]; 初始化不会编译 ,因为复制初始化语法需要非显式构造函数。 其他人将工作。

个人的态度

如果我要从列出的四位候选人中select一个初始化,我会select:

 auto priority = bool{features(w)[5]}; 

因为它引入了一个明确的布尔上下文(在我们想把这个值赋给布尔variables的情况下,这是很好的),并且防止缩小(在其他types的情况下,不易转换为布尔型),这样当一个错误/警告被触发,我们可以诊断什么features(w)[5] 真的是


更新2

我最近观看了2014年CppCon的 Herb Sutter的演讲,题为“ 回归基础”。 现代C ++风格的精华 ,他提出了一些关于为什么应该更喜欢auto x = T{y};显式types初始值设定项的一些观点auto x = T{y}; forms(尽pipe它与auto x = static_cast<T>(y) ,所以不是所有的参数都适用于T x{y}; , 哪个是:

  1. autovariables必须始终被初始化。 也就是说,你不能写auto a; ,就像你可以写容易出错的int a;

  2. 现代C ++风格喜欢右侧的types,就像在:

    a)文字:

     auto f = 3.14f; // ^ float 

    b)用户定义的文字:

     auto s = "foo"s; // ^ std::string 

    c)函数声明:

     auto func(double) -> int; 

    d)命名lambda:

     auto func = [=] (double) {}; 

    e)别名:

     using dict = set<string>; 

    f)模板别名:

     template <class T> using myvec = vector<T, myalloc>; 

    所以 ,再加一个:

     auto x = T{y}; 

    与我们在左侧有名字的样式一致,并且在右侧键入初始化符,可以简单描述为:

     <category> name = <type> <initializer>; 
  3. 使用copy-elision和非显式复制/移动构造函数,与T x{y}语法相比,它具有零成本

  4. 当两种types之间存在细微的差别时,它更加明确:

      unique_ptr<Base> p = make_unique<Derived>(); // subtle difference auto p = unique_ptr<Base>{make_unique<Derived>()}; // explicit and clear 
  5. {}保证不会有隐式转换,也不会缩小。

但是他也提到了一般的auto x = T{}forms的一些缺点,这已经在这篇文章中描述过了:

  1. 即使编译器可以忽略右侧的临时,它也需要一个可访问的,非删除的和非显式的拷贝构造函数:

      auto x = std::atomic<int>{}; // fails to compile, copy constructor deleted 
  2. 如果没有启用elision(例如-fno-elide-constructors elide -fno-elide-constructors ),那么移动不可移动的types会导致昂贵的副本:

      auto a = std::array<int,50>{}; 

我没有在我面前的书,所以我不知道是否有更多的上下文。

但要回答你的问题,不,在这个特定的例子中使用auto + static_cast不是一个好的解决scheme。 它违背了另一个准则(我从来没有看到任何例外的理由):

  • 使用最弱的演员/转换可能。

不必要的强制转换颠覆了types系统,并防止编译器生成诊断消息,以防在程序中的其他地方发生改变,从而影响不兼容的转换。 (远处行动,维修编程的怪胎)

这里static_cast是不必要的强大。 隐式转换将会很好。 所以避免演员。

书中的上下文:

虽然std::vector<bool>概念上持有bool s,但是std::vector<bool>operator[]不返回对容器元素的引用(这是std::vector::operator[]返回的内容除bool以外的每种types)。 相反,它返回一个types为std::vector<bool>::reference (嵌套在std::vector<bool>内部的类)的对象。

当你使用外部库自动化时,没有任何优点,这是更多的错误预防。

我认为,这是这种成语的主要思想。 你应该明确并强制自动行为正确。

顺便说一句,这里关于汽车GotW的好文章。

谁能告诉我,为什么使用这个成语是有好处的?

我能想到的原因是:因为它是明确的。 考虑你将如何(本能地)读取这个代码(即,不知道什么features ):

 bool priority = features(w)[5]; 

“function返回一些通用的”布尔“值的可索引序列;我们读取第五个priority ”。

 auto priority = static_cast<bool>(features(w)[5]); 

“function返回一个可以索引的值序列显式转换为bool ;我们读第五个priority ”。

这段代码并不是为了最短的灵活代码而优化的,而是为了显示结果(显然是一致的 – 因为我认为它不是用auto声明的唯一variables)。

在声明priority使用auto是为了保持代码的灵活性,不pipeexpression式是在右边。

这就是说,我宁愿没有明确的演员版本。