chapter 5
RAII与引用计数
对于动态分配的对象,进行引用计数,每当增加一次对同一个对象的引用,那么引用对象的引用计数就会增加一次, 每删除一次引用,引用计数就会减一,当一个对象的引用计数减为零时,就自动删除指向的堆内存。
C++11 引入了智能指针的概念,使用了引用计数的想法,让程序员不再需要关心手动释放内存。 这些智能指针就包括 std::shared_ptr
/std::unique_ptr
/std::weak_ptr
,使用它们需要包含头文件 <memory>
。
shared_ptr
std::shared_ptr
是一种智能指针,它能够记录多少个 shared_ptr
共同指向一个对象,从而消除显式的调用 delete
,当引用计数变为零的时候就会将对象自动删除。
但还不够,因为使用 std::shared_ptr
仍然需要使用 new
来调用,这使得代码出现了某种程度上的不对称。
std::make_shared
就能够用来消除显式的使用 new
,所以std::make_shared
会分配创建传入参数中的对象, 并返回这个对象类型的std::shared_ptr
指针。例如:
std::shared_ptr
可以通过 get()
方法来获取原始指针,通过 reset()
来减少一个引用计数, 并通过use_count()
来查看一个对象的引用计数。
unique_ptr
std::unique_ptr
是一种独占的智能指针,它禁止其他智能指针与其共享同一个对象,从而保证代码的安全:
既然是独占,换句话说就是不可复制。但是,我们可以利用 std::move
将其转移给其他的 unique_ptr
weak_ptr
std::shared_ptr
依然存在着资源无法释放的问题。
解决这个问题的办法就是使用弱引用指针 std::weak_ptr
,std::weak_ptr
是一种弱引用(相比较而言 std::shared_ptr
就是一种强引用)。弱引用不会引起引用计数增加
chapter 6 正则表达式
正则表达式描述了一种字符串匹配的模式。一般使用正则表达式主要是实现下面三个需求:
- 检查一个串是否包含某种形式的子串;
- 将匹配的子串替换;
- 从某个串中取出符合条件的子串。
正则表达式是由普通字符(例如 a 到 z)以及特殊字符组成的文字模式。模式描述在搜索文本时要匹配的一个或多个字符串。 正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。
普通字符
普通字符包括没有显式指定为元字符的所有可打印和不可打印字符。这包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号。
特殊字符
限定符
std::regex
C++11 提供的正则表达式库操作 std::string
对象, 模式 std::regex
(本质是 std::basic_regex
)进行初始化, 通过 std::regex_match
进行匹配, 从而产生 std::smatch
(本质是 std::match_results
对象)。
[a-z]+\.txt
: 在这个正则表达式中,[a-z]
表示匹配一个小写字母,+
可以使前面的表达式匹配多次, 因此[a-z]+
能够匹配一个小写字母组成的字符串。 在正则表达式中一个.
表示匹配任意字符,而\.
则表示匹配字符.
, 最后的txt
表示严格匹配txt
则三个字母。因此这个正则表达式的所要匹配的内容就是由纯小写字母组成的文本文件。std::regex_match
用于匹配字符串和正则表达式,有很多不同的重载形式。 最简单的一个形式就是传入std::string
以及一个std::regex
进行匹配, 当匹配成功时,会返回true
,否则返回false
。例如:
#include <iostream>
#include <string>
#include <regex>
int main() {
std::string fnames[] = {"foo.txt", "bar.txt", "test", "a0.txt", "AAA.txt"};
// 在 C++ 中 \ 会被作为字符串内的转义符,为使 \. 作为正则表达式传递进去生效,需要对 \ 进行二次转义,从而有 \\.
std::regex txt_regex("[a-z]+\\.txt");
for (const auto &fname: fnames)
std::cout << fname << ": " << std::regex_match(fname, txt_regex) << std::endl;
}
chapter 7 并行与并发
并行基础
std::thread
用于创建一个执行的线程实例,所以它是一切并发编程的基础,使用时需要包含 <thread>
头文件, 它提供了很多基本的线程操作,例如 get_id()
来获取所创建线程的线程 ID,使用 join()
来加入一个线程等等
互斥量与临界区
C++11 引入了 mutex
相关的类,其所有相关的函数都放在 <mutex>
头文件中。
std::mutex
是 C++11 中最基本的 mutex
类,通过实例化 std::mutex
可以创建互斥量, 而通过其成员函数 lock()
可以进行上锁,unlock()
可以进行解锁。 但是在实际编写代码的过程中,最好不去直接调用成员函数, 因为调用成员函数就需要在每个临界区的出口处调用 unlock()
,当然,还包括异常。 这时候 C++11 还为互斥量提供了一个 RAII 语法的模板类 std::lock_guard
。
std::unique_lock
则相对于 std::lock_guard
出现的,std::unique_lock
更加灵活, std::unique_lock
的对象会以独占所有权(没有其他的 unique_lock
对象同时拥有某个 mutex
对象的所有权) 的方式管理 mutex
对象上的上锁和解锁的操作。所以在并发编程中,推荐使用 std::unique_lock
。
std::lock_guard
不能显式的调用 lock
和 unlock
, 而 std::unique_lock
可以在声明后的任意位置调用, 可以缩小锁的作用范围,提供更高的并发度。
如果用到了条件变量 std::condition_variable::wait
则必须使用 std::unique_lock
作为参数。
条件变量
其它杂项
noexcept的修饰和操作
使用 noexcept
修饰过的函数如果抛出异常,编译器会使用 std::terminate()
来立即终止程序运行。
noexcept
还能够做操作符,用于操作一个表达式,当表达式无异常时,返回 true
,否则返回 false
。
noexcept
修饰完一个函数之后能够起到封锁异常扩散的功效,如果内部产生异常,外部也不会触发。
内存对齐
C++ 11 引入了两个新的关键字 alignof
和 alignas
来支持对内存对齐进行控制。
alignof
关键字能够获得一个与平台相关的 std::size_t
类型的值,用于查询该平台的对齐方式。 当然我们有时候并不满足于此,甚至希望自定定义结构的对齐方式,同样,C++ 11 还引入了 alignas
来重新修饰某个结构的对齐方式。