A.4-chinese
A.4 常量表达式函数整型字面值,例如42,就是常量表达式。所以,简单的数学表达式,例如,23x2-4。可以使用其来初始化const整型变量,然后将const整型变量作为新表达的一部分: 1234const int i=23;const int two_i=i*2;const int four=4;const int forty_two=two_i-four; 使用常量表达式创建变量也可用在其他常量表达式中,有些事只能用常量表达式去做: 指定数组长度: 1234int bounds=99;int array[bounds]; // 错误,bounds不是一个常量表达式const int bounds2=99;int array2[bounds2]; // 正确,bounds2是一个常量表达式 指定非类型模板参数的值: 12345template<unsigned size>struct test{};test<bounds> ia; // 错误,bounds不是一个常量表达式test<bounds2> ia...
A.3-chinese
A.3 默认函数删除函数的函数可以不进行实现,默认函数就则不同:编译器会创建函数实现,通常都是“默认”实现。当然,这些函数可以直接使用(它们都会自动生成):默认构造函数,析构函数,拷贝构造函数,移动构造函数,拷贝赋值操作符和移动赋值操作符。 为什么要这样做呢?这里列出一些原因: 改变函数的可访问性——编译器生成的默认函数通常都是声明为public(如果想让其为protected或private成员,必须自己实现)。将其声明为默认,可以让编译器来帮助你实现函数和改变访问级别。 作为文档——编译器生成版本已经足够使用,那么显式声明就利于其他人阅读这段代码,会让代码结构看起来很清晰。 没有单独实现的时候,编译器自动生成函数——通常默认构造函数来做这件事,如果用户没有定义构造函数,编译器将会生成一个。当需要自定一个拷贝构造函数时(假设),如果将其声明为默认,也可以获得编译器为你实现的拷贝构造函数。 编译器生成虚析构函数。 声明一个特殊版本的拷贝构造函数,比如:参数类型是非const引用,而不是const引用。 利用编译生成函数的特殊性质(如果提供了对应的函数,将不会自动生成对...
A.5-chinese
A.5 Lambda函数lambda函数在C++11中的加入很是令人兴奋,因为lambda函数能够大大简化代码复杂度(语法糖:利于理解具体的功能),避免实现调用对象。C++11的lambda函数语法允许在需要使用的时候进行定义。能为等待函数,例如std::condition_variable(如同4.1.1节中的例子)提供很好谓词函数,其语义可以用来快速的表示可访问的变量,而非使用类中函数来对成员变量进行捕获。 最简单的情况下,lambda表达式就一个自给自足的函数,不需要传入函数仅依赖管局变量和函数,甚至都可以不用返回一个值。这样的lambda表达式的一系列语义都需要封闭在括号中,还要以方括号作为前缀: 1234[]{ // lambda表达式以[]开始 do_stuff(); do_more_stuff();}(); // 表达式结束,可以直接调用 例子中,lambda表达式通过后面的括号调用,不过这种方式不常用。一方面,如果想要直接调用,可以在写完对应的语句后,就对函数进行调用。对于函数模板,传递一个参数进去时很常见的事情,甚至可以将可调用对象...
A.6-chinese
A.6 变参模板变参模板:就是可以使用不定数量的参数进行特化的模板。就像你接触到的变参函数一样,printf就接受可变参数。现在,就可以给你的模板指定不定数量的参数了。变参模板在整个C++线程库中都有使用,例如:std::thread的构造函数就是一个变参类模板。从使用者的角度看,仅知道模板可以接受无限个参数就够了,不过当要写这么一个模板或对其工作原理很感兴趣时,就需要了解一些细节。 和变参函数一样,变参部分可以在参数列表章使用省略号...代表,变参模板需要在参数列表中使用省略号: 123template<typename ... ParameterPack>class my_template{}; 即使主模板不是变参模板,模板进行部分特化的类中,也可以使用可变参数模板。例如,std::packaged_task<>(见4.2.1节)的主模板就是一个简单的模板,这个简单的模板只有一个参数: 12template<typename FunctionType>class packaged_task; 不过,并不是所有地方都这...
A.7-chinese
A.7 自动推导变量类型C++是静态语言:所有变量的类型,都会在编译时被准确指定。所以,作为程序员你需要为每个变量指定对应的类型。 有些时候就需要使用一些繁琐类型定义,比如: 123std::map<std::string,std::unique_ptr<some_data>> m;std::map<std::string,std::unique_ptr<some_data>>::iterator iter=m.find("my key"); 常规的解决办法是使用typedef来缩短类型名的长度。这种方式在C++11中仍然可行,不过这里要介绍一种新的解决办法:如果一个变量需要通过一个已初始化的变量类型来为其做声明,那么就可以直接使用auto关键字。这样,编译器就会通过已初始化的变量,去自动推断变量的类型。 1auto iter=m.find("my key"); 当然,auto还有很多种用法:可以使用它来声明const、指针或引用变量。这里使用auto对相关类型进行了声明: 12...
A.8-chinese
A.8 线程本地变量线程本地变量允许程序中的每个线程都有一个独立的实例拷贝。可以使用thread_local关键字来对这样的变量进行声明。命名空间内的变量,静态成员变量,以及本地变量都可以声明成线程本地变量,为了在线程运行前对这些数据进行存储操作: 123456789101112thread_local int x; // 命名空间内的线程本地变量class X{ static thread_local std::string s; // 线程本地的静态成员变量};static thread_local std::string X::s; // 这里需要添加X::svoid foo(){ thread_local std::vector<int> v; // 一般线程本地变量} 由命名空间或静态数据成员构成的线程本地变量,需要在线程单元对其进行使用前进行构建。有些实现中,会将对线程本地变量的初始化过程,放在线程中去做;还有一些可能会在其他时间点做初始化,在一些有依赖的组合中,根据具体情况来进行决定。将没有构造好的线...
10.0-chinese
第10章 多线程程序的测试和调试本章主要内容 并发相关的错误 定位错误和代码审查 设计多线程测试用例 多线程代码的性能 目前为止,我们了解如何写并发代码——可以使用哪些工具,这些工具应该如何使用。不过,在软件开发中重要的一部分我们还没有提及:测试与调试。如果你希望阅读完本章后就能很轻松的去调试并发代码,本章无法满足你的预期。 测试和调试并发代码比较麻烦。除了对一些重要问题的思考,我也会展示一些技巧让测试和调试变得简单一些。 测试和调试就像一个硬币的两面——测试是为了找到代码中可能存在的错误,需要调试来修复错误。如果在开发阶段发现了某个错误,而非发布后发现,这将会将使错误的破坏力降低好几个数量级。 了解测试和调试前,需要了解并发代码可能会出现的问题。
A.9-chinese
A.9 本章总结本附录仅是摘录了部分C++11标准的新特性,因为这些特性和线程库之间有着良好的互动。其他的新特性,包括:静态断言(static_assert),强类型枚举(enum class),委托构造函数,Unicode码支持,模板别名,以及统一的初始化序列。对于新功能的详细描述已经超出了本书的范围;需要另外一本书来进行详细介绍。对标准改动的最好的概述可能就是由Bjarne Stroustrup编写的《C++11FAQ》[1], 其他C++的参考书籍也会在未来对C++11标准进行覆盖。 希望这里的简短介绍,能让你了解这些新功能和线程库之间的关系,并且在写多线程代码的时候能用到这些新功能。虽然,本附录为了新特性提供了足够简单的例子,不过这里还是一个简单的介绍,并非新功能的一份完整的参考或教程。如果想在你的代码中大量使用这些新功能,我建议去找相关权威的参考书或教程,了解更加详细的情况。 【1】 http://www.research.att.com/~bs/C++0xFAQ.html
10.1-chinese
10.1 与并发相关的错误类型你可以在并发代码中发现各式各样的错误,这些错误不会集中于某个方面。不过,有一些错误与使用并发直接相关,本章重点关注这些错误。通常,并发相关的错误通常有两大类: 不必要阻塞 条件竞争 这两大类的颗粒度很大,让我们将其分成颗粒度较小的问题。 ##10.1.1 不必要阻塞 “不必要阻塞”是什么意思?一个线程被阻塞的时候,不能处理任何任务,因为它在等待其他“条件”的达成。通常这些“条件”就是一个互斥量、一个条件变量或一个future,也可能是一个I/O操作。这是多线程代码的先天特性,不过这也不是在任何时候都可取的——衍生成“不必要阻塞”。你会问:为什么不需要阻塞?通常,是因为其他线程在等待该阻塞线程上的某些操作完成,如果该线程阻塞了,那那些线程必然会被阻塞。 这个主题可以分成以下几个问题: 死锁——如你在第3章所见,在死锁的情况下,两个线程会互相等待。当线程产生死锁,应该完成的任务就会持续搁置。举个例子来说,一些线程是负责对用户界面操作的线程,在死锁的情况下,用户界面就会无响应。在另一些例子中,界面接口会保持响应,不过有些任务就无法完成,...
10.2-chinese
10.2 定位并发错误的技术之前的章节,我们了解了与并发相关的错误类型,以及如何在代码中体现出来的。这些信息可以帮助我们来判断,的代码中是否存在有隐藏的错误。 最简单直接的就是直接看代码。虽然看起来比较明显,但是要彻底的修复问题,却是很难的。读刚写完的代码,要比读已经存在的代码容易的多。同理,当在审查别人写好的代码时,给出一个通读结果是很容易的,比如:与你自己的代码标准作对比,以及高亮标出显而易见的问题。为什么要花时间来仔细梳理代码?想想之前提到的并发相关的问题——也要考虑非并发问题。(也可以在很久以后做这件事。不过,最后bug依旧存在)我们可以在审阅代码的时候,考虑一些具体的事情,并且发现问题。 即使已经很对代码进行了很详细的审阅,依旧会错过一些bug,这就需要确定一下代码是否做了对应的工作。因此,在测试多线程代码时,会介绍一些代码审阅的技巧。 10.2.1 代码审阅——发现潜在的错误在审阅多线程代码时,重点要检查与并发相关的错误。如果可能,可以让同事/同伴来审阅。因为不是他们写的代码,他们将会考虑这段代码是怎么工作的,就可能会覆盖到一些你没有想到的情况,从而找出一些...













