/***********************************************************
关于书:
书是我从网上找到的effective Modern C++的样章,内容只到条款4就没有了,
所以现阶段我只能翻译到条款4,不过以后有机会我会继续翻译的。
如果读者找到了完整的版本,欢迎大家发给我。1021842556@qq.com
effective Modern C++的样章的下载地址http://pan.baidu.com/s/1ntKBlpf 提取密码是upkw
关于我:
一名大四的学生,喜欢读书,读好书,求甚解,爱好技术,大学期间也看过了5本技术类的英文书籍,
但翻译英文书的内容还是第一次,所以有些地方翻译的可能不是很到位,还请大家见谅。
感谢郝同学帮助我进行错别字以及语句是否通顺的检查。
*************************************************************/
介绍
如果你是一个有经验的C++程序员,就像我一样,你最初接触C++11的时候会想“恩恩,我明白了,这还是C++,只是多了些东西而已”但是随着你对这个修订后的语言的了解逐渐增长,你会对它的变化之大感到震惊,auto类型,基于范围的for循环,lambada表达式,右值引用这些东西已经改变了C++的面貌,更不用说新增加的并发的特性。还有那些更顺应语言习惯的改变,将0作为指针空值造成的二义性已经不再存在,定义别名也不再是typedef的专属能力,nullptr和新的别名的声明方式加入了进来;枚举有了强类型的作用域;智能指针也比内置的指针更可取了,移动构造通常优于拷贝构造,要想了解C++11,你还有很多东西去学习。
C++14标准已经确认,但麻烦并没有迎刃而解。
所以,有很多的东西需要学习,更重要的是,去学习如何有效率的使用这些新的特性,如果你需要有关现代C++基本语法和语义特性的信息,你可以找到大量的资料,但是你如果想要找到一些指导方针,关于如何利用这些新的特性来创造那些正确的,有效率的,可维护的,可移植的软件的话,这会是一个更有挑战事情,而这就是这本书的目的,它致力于如何有效率的使用那些C++11和C++14的新特性,而不只是单纯的描述这些特性。
这本书里的信息被分割为一条一条的条款,想要明白类型推导的各种形式?想要知道什么时候应该(什么时候不应该)把一个对象声明为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是一个左值,尽管它有一个(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> // 这是C++代码里的
void processVals(const Ts&... params) // 省略号
{
… // 意味指这里还有一些代码
}
processVals声明显示了我在声明模板参数的时候使用了typename,这只是个人的偏爱,class在这里同样适用,仅仅在我展示一些来自C++标准中的代码引用的时候,我会使用class声明模板的参数,因为标准里就是这样做的。
当一个对象以另一个同样类型的对象初始化的时候,这个新的对象被认为原对象的一个拷贝,即使这个拷贝是经由移动构造创建的,令人遗憾的是,C++中没有任何一个技术可以区分一个对象是经由拷贝构造创建的,还是经由移动构造创建的。
void someFunc(Widget w); // someFunc的参数
// 是经由值传递 Widget wid; // wid是Widget类型的对象
someFunc(wid); // 在这个函数调用中,
// w是wid经由拷贝构造创建的
// 一个拷贝
someFunc(std::move(wid)); // 在这个函数调用中
// w是wid经由移动构造创建的
// 一个拷贝
右值的拷贝通常是通过移动构造的,左值的拷贝通常是通过拷贝构造的,这里暗示了我们,如果你仅仅知道一个对象是另一个对象的一个拷贝,你无法知道构造这个拷贝的花费,比如在上面的代码中,当你不知道是一个左值还是一个右值被传递给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; // 对象声明
class Widget; // 类声明
int func(const Widget& w); // 函数声明
enum class Color; // 有作用域的枚举的声明
// (见条款10)
定义提供了它的储存空间和它的实现细节。
int x; // 对象定义 class Widget { // 类定义
…
};
int func(const Widget& w)
{ return w.size(); } // 函数定义
enum class Color
{ Yellow, Red, Blue }; // 有作用域的枚举的定义
定义同样包括声明,所以除非某些东西当它作为定义很重要时,一般情况下,我倾向于使用声明。
新的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
/***********************************************************
才发现博客园6小时内只能在网站首页发布1篇随笔,
所以下一篇文章在这里Effective Modern C++翻译(2)-条款1
***********************************************************/