为什么我应该更喜欢“明确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]
在窗体中发生的初始化
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};
。
对我来说,这听起来更像是一个警告:
使用
auto
types推断很酷,但如果使用不当,可能会导致不确定的行为。
作为涉及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() { /*...*/ } };
签名的改变closures了auto 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};
, 哪个是:
-
auto
variables必须始终被初始化。 也就是说,你不能写auto a;
,就像你可以写容易出错的int a;
-
现代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>;
-
使用copy-elision和非显式复制/移动构造函数,与
T x{y}
语法相比,它具有零成本 。 -
当两种types之间存在细微的差别时,它更加明确:
unique_ptr<Base> p = make_unique<Derived>(); // subtle difference auto p = unique_ptr<Base>{make_unique<Derived>()}; // explicit and clear
-
{}
保证不会有隐式转换,也不会缩小。
但是他也提到了一般的auto x = T{}
forms的一些缺点,这已经在这篇文章中描述过了:
-
即使编译器可以忽略右侧的临时,它也需要一个可访问的,非删除的和非显式的拷贝构造函数:
auto x = std::atomic<int>{}; // fails to compile, copy constructor deleted
-
如果没有启用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式是在右边。
这就是说,我宁愿没有明确的演员版本。