北京时间2016年1月9日10:31:06。正式開始翻译。水平有限,各位看官若有觉得不妥之处,请批评指正。
之前已经有人翻译了前几个条目,有些借鉴出处:http://www.cnblogs.com/magicsoar/p/3966177.html?utm_source=tuicool&utm_medium=referral
如今就開始《Effective Modern C++》翻译之旅,第一个姿势--简单介绍
Introduction
假设您是一位专家级别的C++project师,和我最初接触C++11的时候想法一样:“是的,我明白了,C++11仅仅只是是比C++多了一点东西而已”。
可是随着您对这个修订后语言的理解加深,你便会被它的变化之大感到震惊。自己主动类型判断、基于范围的for循环、lambda表达式、右值引用等改变了C++的面貌,更不要说新添加的并发特性。
还有那些符合语言特性习惯的变化。0和typedef关键字被淘汰,取而代之的是nullptr表示空指针和别名声明(alias declaration)。枚举有了作用域,智能指针成为了更加完美的内置类型。移动对象往往比拷贝对象更加靠谱。所以,C++11有非常多值得学习的东西。
C++14的通过并没有让这些变得简单。
所以。须要学习的地方非常多。更重要的是,怎样高效的使用这些新特性。
假设您须要C++11的主要的语法和语义,资源总量比較丰富,但假设你正在寻找怎样指导您编写正确的,有效的,可维护代码的资料时。这种搜索非常具挑战性。这就是这本书的原因。
这里不是专门描写叙述的C ++ 11和C ++14的特性,而是告诉你怎样有效的应用C++11。
这本书里的信息分为一条一条的,我们称之为条款。
想要理解变量的类型判断?或者想要明白什么时候应该(或者什么时候不应该)使用auto来声明变量?您对为什么const成员函数应该是线程安全的感兴趣?怎样使用std::unique_ptr实现pimpl?为什么你在使用lambda表达式时应该避免默认的变量捕捉形式?或者是std::atomic和volatile的差别(怎样正确的使用它们)?这些问题的答案你都能够在书中找到,除此之外。这些答案都是平台独立,与标准一致的,这是一本关于C++的可移植的书。
每个条款都是一个指导方针,而不是准则,这是由于指导方针是有例外的。
每个条款中最主要的部分不在于它提出的建议,而是这些建议背后的原理和思考的过程。一旦你读完了这本书。将来是由你来决定在你的项目的环境中,是否应该忽视或者应用这些条款中的指导。这本书的真正目的不在于告诉你应该做什么、避免做什么。而是传递对C++11和C++14怎样工作的更深层次的理解。
术语和习惯
为了确保我们彼此理解,在一些术语上达成一致非常重要。C++有4个标准,命名规则是被ISO标准採用的年份,C++98,C++03,C++11和C++14。C++98和C++03仅仅是存在一些微妙的技术细节上的差别,所以在这本书里,我把二者都称为C++98。
当我提到C++98的时候,我指的仅仅是C++语言的这个版本号。
在我提到C++11的地方,我指的是C++11和C++14。由于C++14是C++11的一个有效的超集。当我写C++14的时候,我明白的指的是C++14,假设我仅仅是简单的提到C++。那么它是属于全部语言版本号的。所以,我可能会说C++是十分重视效率的(这里指的是全部的C++版本号), C++98缺少对并发性的支持(指的仅仅是C++98), C++11支持了lambda表达式(指的C++11和C++14),C++14提供了更普遍的函数返回类型的推导(指的仅仅是C++14)。
C++11最流行的特性非常可能是移动语义,移动语义的基础是区分表达式中的是左值还是右值。由于右值暗示了对象有资格使用移动运算,而左值通常不能。
在概念上(虽然并不总是在实践中)右值相相应于从函数返回的匿名的暂时变量,而左值相相应于你能够引用的对象。既能够通过指针。也能够通过引用。
来判断一个表达式是不是左值的有效方法是看你能不能取它的地址。假设能的话。它通常就是一个左值。
假设你不能的话。它一般是一个右值。这种方法的一个好的特性在于它帮助你记住了一个表达式的类型和这个表达式代表的是一个左值还是一个右值是无关的。给一个类型T,你既能够获得T的左值类型,也能够获得T的右值类型,这是十分重要的,尤其是当你处理一个右值的引用參数的时候,由于这个时候參数本身是一个左值。
class Widget {
public:
Widget(Widget&& rhs); // rhs is an lvalue, though it has
.... //an rvalue reference type
};
这里,在widgt的移动构造函数中取得rhs參数的地址是全然合法的,所以rhs是一个左值,虽然它的类型是一个右值的引用(相似的推理。一切參数都是左值)。
这段代码展示了非常多我通常遵循的约定:
•类的名字是widget,当我想要表示一个随意的用户自己定义类型的时候使用widget。
我会不加声明的使用widget。除了某些时候,我须要展示类的特殊的细节。
•我把參数命名为rhs。代表了right-hand side,这是我在使用移动操作(比方移动构造。移动赋值)和拷贝操作(比方拷贝构造,拷贝赋值)时比較偏爱的名字,虽然我在使用二元运算符也通常使用rhs作为右面參数的名字。
Matrix operator+(const Matrix& lhs,const Matrix& rhs);
我希望这不会令你感到吃惊。lhs代表了left-hand side。
•我高亮了代码或者凝视的部分内容。来使你的注意力集中到上面去,在上面的代码中。我加亮了rhs和凝视的部分内容,使你注意到rhs是一个左值。
•我使用“…”来暗示这里会有其它的代码,这里窄的省略号和宽的省略号(“…”)间是有差别的。宽的省略号是在C++11中作为变长模板使用的,这听起来有点令人困惑,事实上不是,比如
template<typename... Ts> // these are C++
void processVals(const Ts&... params) // source code
{ // ellipses 28
… // this means "some
// code goes here"
}
processVals声明显示了我在声明模板參数的时候使用了typename,这仅仅是个人的偏爱。class在这里相同适用,仅仅在我展示一些来自C++标准中的代码引用的时候,我会使用class声明模板的參数,由于标准里就是这样做的。
当一个对象以还有一个相同类型的对象初始化的时候,这个新的对象被觉得原对象的一个拷贝,即使这个拷贝是经由移动构造创建的,令人遗憾的是。C++中没有不论什么一个技术能够区分一个对象是经由拷贝构造创建的。还是经由移动构造创建的。
void someFunc(Widget w); // someFunc's parameter w
// is passed by value
Widget wid; // wid is some Widget
someFunc(wid); // in this call to someFunc,
// w is a copy of wid that's
// created via copy construction
someFunc(std::move(wid)); // in this call to SomeFunc,
// w is a copy of wid that's
// created via move construction
右值的拷贝一般是通过移动构造的。左值的拷贝一般是通过拷贝构造的。这里暗示了我们。假设你仅仅知道一个对象是还有一个对象的一个拷贝,你无法知道构造这个拷贝的花费。比方在上面的代码中,当你不知道是一个左值还是一个右值被传递给someFunc的參数w的时,你无法知道创建參数w所须要的花费(你相同须要知道拷贝构造和一个构造widget的花费)。
在一个函数调用中。调用端的表达式是这个函数的实參(argument),这些參数被用来实例化函数的形參(parameters)。在第一个样例中,实參是wid,在第二个样例中。实參是std::move(wid)。
在这两个样例中, 形參都是w。形參和实參的差别是非常重要的。由于形參是左值。可是实參和实例化这些实參的却可能是左值或是右值,这个和完美转发(perfect forwarding)的过程相关。
完美转发是指将參数传递给函数中调用的第二个函数,原来參数的左值和右值性得以保留(将在条款32中进行讨论完美转发的很多其它细节)。
精心设计的函数是异常安全的(exception-safe),这意味着他们至少提供了最主要的异常安全保证(即基本承诺basic guarantee)。这种函数向调用者确保了即使有一个异常产生了。程序的不变量依然是完整的(即没有不论什么数据结构被破坏)。也没有不论什么资源的泄露。那些提供了强烈的异常安全保证(即强烈保证strong guarantee)的函数。向调用者确保了假设有一个异常产生了,程序的状态和调用前是一样的。就像条款16解释的那样,C++98标准类库里的函数提供了强烈保证约束对于C++11标准类库里的移动语义(C++98 Standard Library functions offering the strong guarantee constrain the applicability of move semantics in the C++11 Standard Library.)。
我使用术语可调用实体(callable entity)来描写叙述能够和调用非成员函数一样的调用语法的不论什么东西,比方。语法“functionName(arguments)“,函数,函数指针,函数对象都是可调用实体(callable entity)。
通过lambda表达式创建的函数对象被称为闭包(closures),非常少有必要去区分一个lambda表达式和它们创建的闭包,所以我把它们都称作lambdas。
相同的,我差点儿不区分函数模板(即产生函数的模板)和模板函数(即从模板里实例化的函数)。类模板和模板类也一样。
C++里的非常多东西能够被声明和定义,声明给出了它的名字,却没有给出太多的细节,比方它的储存空间和它是怎样实现的。
extern int x; // object declaration
class Widget; // class declaration
int func(const Widget& w); // function declaration
enum class Color; // scoped enum declaration
// (see Item 10)
定义提供了它的储存空间和它的实现细节。
class Widget { // class definition
…
};
int func(const Widget& w)
{ return w.size(); } // function definition
enum class Color
{ Yellow, Red, Blue }; // scoped enum definition
定义相同包含声明,所以除非某些东西当它作为定义非常重要时,普通情况下,我倾向于使用声明。
新的C++标准保留了原有的在旧的标准下写的代码的有效性,可是标准委员会偶尔也会弃用(deprecates)一些特性。这警告一个特性可能会在未来的标准中被移除,你应该避免使用这些被否决的特性(被否决的原因一般是新的特性提供了一样的功能。可是带有更少的限制和缺点),比如std::auto_ptr在C++11中被否决。由于std::unique_ptr提供了相同的功能,并且做的更好。
有时,标准会说一个操作的结果是没有定义的(undefined behavior),这意味着执行时的行为是无法预測的。毫无疑问。你想要避开这种不确定性,没有定义的行为有使用中括号([])时下标超过了std::vector的界限,解引用一个未实例化的迭代器,或者涉及到数据竞争(比如有两个以上的线程,至少一个是写者。同一时候訪问一个内存单元)。
在源码的凝视中。有时我把construct缩写为ctor。把destructor缩写为dtor。
报告Bugs和改进建议
我尽我最大努力让这本书充满了清晰,详细。实用的信息,可是肯定还有一些方式使它变的更好。
怎样你发现了不论什么形式的错误(技术的。解释说明的。语法的,排版的等等),或者你有关于改进这本书的建议,请发邮件到我的邮箱 emc++@aristeia.com 。新的印刷给我机会来修订Effective Modern C++,但我无法解决我不知道的问题。
怎样想查看我已知的问题的列表,能够查阅本书的勘误页,网址是:
http://www.aristeia.com/BookErrata/emc++-errata.html
==============================================================
译者凝视:
Pimpl Idiom 它能够用来减少文件间的编译依赖关系