您的位置:首页 > 编程语言 > C语言/C++

《Effective Modern C++》翻译--简介

2016-01-09 12:52 387 查看
[code]北京时间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++工程师,和我最初接触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的右值类型,这是十分重要的,尤其是当你处理一个右值的引用参数的时候,因为这个时候参数本身是一个左值。

[code]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作为右面参数的名字。

[code]Matrix operator+(const Matrix& lhs,const Matrix& rhs);


我希望这不会令你感到惊讶,lhs代表了left-hand side。

•我高亮了代码或者注释的部分内容,来使你的注意力集中到上面去,在上面的代码中,我加亮了rhs和注释的部分内容,使你注意到rhs是一个左值。

•我使用“…”来暗示这里会有其他的代码,这里窄的省略号和宽的省略号(“…”)间是有区别的,宽的省略号是在C++11中作为变长模板使用的,这听起来有点令人困惑,其实不是,例如

[code]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++中没有任何一个技术可以区分一个对象是经由拷贝构造创建的,还是经由移动构造创建的。

[code]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++里的很多东西可以被声明和定义,声明给出了它的名字,却没有给出太多的细节,比如它的储存空间和它是如何实现的。

[code]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)


定义提供了它的储存空间和它的实现细节。

[code]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

==============================================================

[code]译者注释:
Pimpl Idiom  它可以用来降低文件间的编译依赖关系
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: