0%

源码链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <iostream>
#include <map>
#include <string>
#include <vector>

/**
* @brief 把字符串前后的字符串给去除
* @param s [in] 要剪切的字符串
* @param chars [in] 要去除什么的字符串
* @return std::string& 剪切后的字符串
* @author lijiancong(lijiancong@gbcom.com.cn)
* @note
*/
static std::string& strip(std::string& s, const std::string& chars = " ") {
s.erase(0, s.find_first_not_of(chars.c_str()));
s.erase(s.find_last_not_of(chars.c_str()) + 1);
return s;
}

/**
* @brief 以特定符号为分隔符,切分字符串并放入vector里
* @param s [in] 原字符串
* @param tokens [out] 剪切后的子字符串
* @param delimiters [in] 分隔符
* @author lijiancong(lijiancong@gbcom.com.cn)
* @note
*/
static void split(const std::string& s, std::vector<std::string>& tokens,
const std::string& delimiters = " ") {
std::string::size_type lastPos = s.find_first_not_of(delimiters, 0);
std::string::size_type pos = s.find_first_of(delimiters, lastPos);
while (std::string::npos != pos || std::string::npos != lastPos) {
tokens.push_back(s.substr(lastPos, pos - lastPos));
lastPos = s.find_first_not_of(delimiters, pos);
pos = s.find_first_of(delimiters, lastPos);
}
}

static void parse(std::string& s, std::map<std::string, std::string>& items) {
std::vector<std::string> elements;
s.erase(0, s.find_first_not_of(" {"));
s.erase(s.find_last_not_of("} ") + 1);
split(s, elements, ",");
for (auto& iter : elements) {
std::vector<std::string> kv;
split(iter, kv, ":");
if (kv.size() != 2) continue;
items[strip(kv[0], " \"")] = strip(kv[1], " \"");
}
}

int main() {
std::string data =
" { \"key1\" : \"data1\" , \"key2\" : \"data2\" } ";
std::map<std::string, std::string> items;
parse(data, items);

for (const auto& iter:items){
std::cout << "key=" << iter.first << ",value=" << iter.second << std::endl;
}
system("pause");
}

thread_pool 源码学习

rotating_file_sink定义

rotating_file_sink.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//
// Rotating file sink based on size
//
template<typename Mutex>
class rotating_file_sink final : public base_sink<Mutex>
{
public:
rotating_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files, bool rotate_on_open = false);
static filename_t calc_filename(const filename_t &filename, std::size_t index);
filename_t filename();

protected:
void sink_it_(const details::log_msg &msg) override;
void flush_() override;

private:
// Rotate files:
// log.txt -> log.1.txt
// log.1.txt -> log.2.txt
// log.2.txt -> log.3.txt
// log.3.txt -> delete
void rotate_();

// delete the target if exists, and rename the src file to target
// return true on success, false otherwise.
bool rename_file_(const filename_t &src_filename, const filename_t &target_filename);

filename_t base_filename_; ///< 基础文件名称
std::size_t max_size_; ///< 最大单个文件大小
std::size_t max_files_; ///< 最大日志文件数量
std::size_t current_size_; ///< 当前文件的大小
details::file_helper file_helper_; ///< 用于辅助写文件的对象
};

构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

/// @name rotating_file_sink
/// @brief 构造本对象,
/// 1. 打开日志文件
/// 2. 获取当前文件大小
/// 3. 如果超出了单个文件大小,则重新创建文件并打开
///
/// @param base_filename [in] 基础的文件名
/// @param max_size [in] 最大单个文件大小
/// @param max_files [in] 最大的文件
/// @param rotate_on_open [in] 是否在文件打开时rotate
///
/// @date 2020-07-04 21:51:27
rotating_file_sink(std::string base_filename, std::size_t max_size,
std::size_t max_files, bool rotate_on_open = false)
: base_filename_(std::move(base_filename)),
max_size_(max_size),
max_files_(max_files) {
/// 打开当前应该写入的文件,并由file_helper对象来持有这个文件指针
file_helper_.open(calc_filename(base_filename_, 0));
/// 该函数时间执行时间很长,在这里只执行一次。
current_size_ = file_helper_.size();
/// 假如允许rotate且当前文件大小大于零
if (rotate_on_open && current_size_ > 0) {
/// 执行一次rotate
rotate_();
}
}

rotate_()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/// @name     rotate_
/// @brief 执行循环日志文件的创建
/// @details
/// Rotate files:
/// log.txt -> log.1.txt
/// log.1.txt -> log.2.txt
/// log.2.txt -> log.3.txt
/// log.3.txt -> delete
///
/// @param NONE
///
/// @return NONE
///
/// @date 2020-07-04 21:58:24
inline void rotate_() {
/// 首先关闭该文件
file_helper_.close();
/// 开始查找要创建的下一个日志文件名称
for (auto i = max_files_; i > 0; --i) {
/// 拼装出上一个该文件名称
std::string src = calc_filename(base_filename_, i - 1);
if (!path_exists(src)) {
/// 该文件如果不存在则到下一个循环
continue;
}
/// 这里找到要创建的日志名称了日志文件的名称
std::string target = calc_filename(base_filename_, i);

/// 把上一个文件改名为当前的文件名
if (!rename_file_(src, target)) {
/// 如果失败则在一个短暂的延迟后再次尝试
sleep_for_millis(100);
if (!rename_file_(src, target)) {
/// 关闭并打开这个日志文件,防止它增长超出限制
file_helper_.reopen(true);
current_size_ = 0;
/// 抛出异常
throw("rotating_file_sink: failed renaming ");
}
}
}
/// 以追加模式("a")重新打开这个文件
file_helper_.reopen(true);
}

虚函数的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/// @name   sink_it_
/// @brief 写文件日志的函数,如果写入日志大于最大文件大小则创建下一个文件
///
/// @param msg [in] 写入的日志信息
///
/// @return
///
/// @date 2020-07-05 09:33:12
void sink_it_(const details::log_msg &msg) override {
std::string formatted;
/// 拼装日志信息
base_sink<Mutex>::formatter_->format(msg, formatted);
/// 计算这条日志加上原本大小是否超过了最大文件大小
current_size_ += formatted.size();
if (current_size_ > max_size_) {
/// 超过了就创建下一个文件
rotate_();
/// 更新为当前文件大小为这个日志信息的大小
current_size_ = formatted.size();
}
/// 如果没有超过最大大小则继续写该文件
file_helper_.write(formatted);
}

/// @name flush_
/// @brief 刷新文件
///
/// @param NONE
///
/// @return NONE
///
/// @date 2020-07-05 09:36:23
void flush_() override { file_helper_.flush(); }

异常处理

异常处理exception handling)机制允许程序独立开发的部分能够在运行时就出现问题进行通信并作出相应的处理。异常是的我们能够将问题的检测和解决过程分离开来。程序的一部分负责检测问题的出现,然后解决该问题的任务传递给程序的另一部分。检测环节无需知道问题处理模块的所有细节,反之亦然。

1. 抛出异常

​在C++语言中,我们通过抛出(throwing)一条表达式来引发(raised)一个异常。被抛出的表达式的类型以及当前的调用链共同决定了哪段处理代码(handler)将被用来处理该异常。被选中的处理代码实在调用链中与抛出对象类型匹配的最近的处理代码。其中,根据抛出对象的类型和内容,程序的异常抛出部分会告知异常处理部分到底发生了什么错误。

​当执行一个throw时,跟在throw后面的语句将不再被执行。相反,程序的控制权从throw转移到与之匹配的catch模块。该catch可能是同一函数中的局部catch也可能位于直接或间接调用了发生异常的函数的另一个函数中。控制权从一处转移到另一处,这有两个重要的含义:

  • 沿着调用链的函数可能会提早退出
  • 一旦程序开始执行异常处理代码,则沿着调用链创建的对象将被销毁

因为跟在throw后面的语句将不再被执行,所以throw语句的有类似于return语句:它通常作为条件语句的一部分或者作为某个函数的最后(或者唯一)一条语句。

1.1 栈展开

当抛出一个异常后,程序暂停当前函数的执行过程并立即开始寻找与异常匹配的catch子句。

  • throw出现在一个try语句块(try block)内时,检查与该try块关联的catch子句。

  • 如果找到了匹配的catch,就使用该catch处理异常。

  • 如果这一步没找到匹配catch且该try语句嵌套在其他try块中,则继续检查与外层try匹配的catch子句

  • 如果还是找不到匹配catch,则退出当前函数,在调用当前函数的外层函数中继续寻找。

  • 如果对抛出异常的函数的调用语句位于一个try语句块内,则检查与该try块关联的catch子句。

  • 如果找到了匹配的catch,就使用该catch处理异常。

  • 否则,如果该try语句嵌套在其他try块中,则继续检查与外层try匹配的catch子句。

  • 如果仍然没找到匹配的catch,则退出当前这个主调函数,继续在调用刚刚退出的这个函数的其他函数中寻找,以此类推。

​上述过程被称为栈展开(stack unwinding)过程。栈展开过程沿着嵌套函数的调用链不断查找,直到找到了与异常匹配的catch子句为止;或者也可能一致没找到匹配的catch,则退出主函数后过程中止。

​假设找到了一个匹配的catch子句,则程序进入该子句并执行其中代码。当执行完这个catch子句后,找到与try块关联的最后一个catch子句后的点,并从这里继续执行。

如果没有找到匹配的catch子句,程序将退出。因为异常通常被认为是妨碍程序正常执行的事件,所以一旦引发了某个异常,就不能对它置之不理。当找不到匹配的catch时,程序将调用标准库函数terminate,顾名思义,terminate负责中止程序的执行过程。

1.2 栈展开过程中对象被自动销毁

​在栈展开过程中,位于调用链上的语句块可能会提前退出。如果在栈展开过程中退出了某个块,编译器将负责确保在这个块中创建的对象都能被正确的销毁。如果某个局部对象的类型是类类型,则该对象的析构函数将被自动调用。与往常一样,编译器在销毁内置类型的对象时不需要做任何事情。

​如果异常发生在构造函数中,则当前的对象可能只构造了一部分。有的成员已经开始初始化了,而另外一些成员在异常发生前也许还没有开始初始化。即使某个对象只构造了一部分,我们也要确保构造的成员能被正确的销毁(否则会发生内存泄露)。

​类似的,异常也可能发生在数组标准库容器的元素初始化过程中。与之前类似,如果在异常发生前已经构造了一部分元素,则我们应该确保这部分元素被正确的销毁。

1.3 析构函数与异常

​析构函数总是会被执行的,但是函数中负责释放资源的代码却可能会被跳过。如果一个块分配了资源,并且在负责释放这些资源的代码前面发生了异常,则释放资源的代码将不会被执行。另一方面,类对象分配的资源将由类的析构函数负责释放。因此,如果我们使用类来控制资源的分配,就能确保无论函数正常结束还是遭遇异常,资源都能被正确地释放。(RAII的思想,在构造函数中获取资源(i.e new),在析构函数中释放资源(i.e delete)。)

​所以出于栈展开可能使用析构函数的考虑,析构函数不应该抛出不能被它自身处理的异常。换句话说,如果析构函数需要执行某个可能抛出异常的操作,则该操作应该被放置在一个try语句块当中,并且在析构函数内部得到处理(如果不这样做的话,程序会马上被终止)。

注:所有标准库类型都能保证它们的析构函数不会引发异常。

1.4 异常对象

异常对象exception object)是一种特殊的对象,编译器使用异常抛出表达式来对异常对象进行拷贝初始化。因此throw语句中的表达式必须拥有完整类型。而且如果该表达式是类类型的话,则相应的类必须含有一个可访问的析构函数和一个可访问的拷贝或移动构造函数。如果该表达式是数组类型函数类型,则表达式将被转换成与之对应的指针类型

​异常对象位于有编译器管理的空间中,编译器确保无论调用哪个catch子句都能访问该空间。异常处理完毕后,异常对象被销毁。

​当一个异常被抛出是,沿着调用链的块将依次退出直至找到与异常匹配的处理代码。如果退出某个块,则同时释放块中局部对象使用的内存。因此,抛出一个指向局部对象的指针几乎肯定是一种错误行为。如果指针所指的对象位于某个块中,而该块在catch语句之前就已经退出了,则意味着在执行catch语句之前局部对象已经被销毁了。

​当我们抛出一条表达式时,该表达式的静态编译时类型决定了异常对象的类型。很多情况下程序抛出的表达式类型来自于某个继承体系。如果一条throw表达式解引用一个基类指针,而该指针实际指向的是派生类对象,则抛出的对象将被切掉一部分,只有基类部分被抛出

注: 抛出指针要求在任何对应处理代码存在的地方,指针所指的对象都必须存在。

2. 捕获异常

catch子句(catch clause)中的一场声明(exception declaration)看起来像是只包含一个形参的函数形参列表。像在形参列表中一样,如果catch无须访问抛出的表达式的话,则我们可以忽略捕获形参的名字。

声明的类型决定了处理代码所能捕获的异常类型。这个类型必须是完全类型,它可以是左值引用,不能是右值引用。当进入一个catch语句后,入参通过异常对象初始化异常声明中的参数。和函数的参数类似,如果catch的参数类型是非引用类型,则该参数是异常对象的一个副本,如果参数是引用类型,则和其他引用参数一样,该参数是异常对象的一个别名。

如果catch的参数是基类类型,则我们可以使用其派生类类型的异常对象对其进行初始化。此时,如果catch的参数是非引用类型,则异常对象将被切掉一部分,如果catch的参数是基类的引用,则该参数将以常规方式绑定到异常对象上。

最后一点需要注意的是,异常声明的静态类型将决定catch语句所能执行的操作。如果catch的参数是基类类型,则catch无法使用派生类特有的任何成员。

Tips: 通常情况下,如果catch接收的异常与某个继承体系有关,则最好将该catch的参数定义成引用类型。

2.1 查找匹配的处理代码

​在搜寻catch语句的过程中,我们最终找到的catch未必是异常的最佳匹配。相反,挑选出来的应该是第一个与异常匹配的catch语句。因此,越是专门的catch越应该置于整个catch列表的前端。

​因为catch语句是按照其出现的顺序逐一匹配的,所以当程序员使用具有继承关系的多个异常时必须对catch语句的顺序进行组织管理,是的派生类异常的处理代码出现在基类异常的处理代码异常之前。

​与实参和形参的匹配规则相比,异常和catch异常声明的匹配规则受到更多限制。此时,绝大多数类型转换都不被允许,除了一些极细小的差别之外,要求异常的类型和catch声明的类型时精确匹配的:

  • 允许从非常量的类型转换,也就是说一条非常量对象的throw语句可以匹配一个接受常量引用的catch语句
  • 允许从派生类向基类的类型转换。
  • 数组被转换成指向数组(元素)类型的指针,函数被转化成指向该函数类型的指针。

除此之外,包括标准算术类型转换和类类型转换在内,其他所有转换规则都不能在匹配catch的过程中使用。

如果在多个catch语句的类型之间存在着继承关系,则我们应该把继承链最低端的类(most derived type)放在前面,而将继承链最顶端的类(least derived type)放在后面。

2.2 重新抛出

​一个单独的catch语句不能完整的处理某个异常。在执行了某些校正操作之后,当前的catch可能会决定由调用链更上一层的函数接着处理异常。一条catch语句通过重新抛出的操作将异常传递给另外一个catch语句。这里的重新抛出仍然是一条throw语句,只不过不包含任何表达式: throw;

​空的throw语句只能出现在catch语句或catch语句直接或间接调用的函数之内。如果在处理代码之外的区域遇到了空throw语句,编译器将调用terminate

​一个重新抛出语句并不指定新的表达式,而是将当前的异常对象沿着调用链向上传递。

​很多时候,catch语句会改变其参数内容。如果在改变了参数的内容后catch语句重新抛出异常,则只有当catch异常声明是引用类型时我们对参数所作的改变才会被保留并继续传播。

2.3 捕获所有异常的处理代码

​为了一次性捕获所有异常,我们使用省略号作为异常声明,这样的处理代码称为捕获所有异常的处理代码,形如catch(...).

catch(...)通常与重新抛出语句一起使用,其中catch执行当前局部能完成的工作,随后重新抛出异常。

Tips: 如果catch(...)与其他几个catch语句一起出现,则catch(...)必须在最后的位置。出现在捕获所有一场语句后面的catch语句将永远不会被匹配。

3. 函数try语句块与构造函数

​通常情况下,程序执行的任何时刻都可能发生异常,特别是一场可能发生在处理构造函数初始值的过程中。构造函数在进入其函数体之前首先执行初始值列表。因为在初始值列表抛出异常时构造函数体内的try语句块还未生效,所以构造函数体内的catch语句无法处理构造函数初始值列表抛出的异常。

​要想处理构造函数初始值抛出的异常,我们必须将构造函数写成函数try语句块function try block)的形式。函数try语句使得一组catch语句既能处理构造函数体(或析构函数体),也能处理构造函数的初始化过程(或析构函数的析构过程)。

1
2
3
4
5
6
7
8
9
template <typename T>
Blob<T>::Blob(std::initializer_list<T> il)
try
: data(std::make_shared<std::vector<T>>(il))
{/** ... */}
catch (const std::bad_alloc &e)
{
handle_out_of_memory(e);
}

4. noexcept 异常说明

1
2
void recoup() noexcept; /** 不会抛出异常 */
void alloc(); /** 可能会抛出异常 */
  • 对于一个函数来说,noexcept说明要么出现在该函数的所有声明语句和定义语句中,要么一次也不出现。该说明应该在函数应该在函数的尾置返回类型之前。

  • 我们也可以在函数指针的声明和定义中指定noexcept

  • 在typedef或类型别名中则不能出现noexcept

  • 在成员函数中,noexcept说明符需要跟在const及引用限定符之后,而在finaloverride或虚函数=0之前。

4.1 违反异常说明

​编译器并不会在编译时检查noexcept说明。实际上,如果一个函数说明了noexcept的同时又含有throw语句或者调用了可能抛出异常的其他函数,编译器将顺利通过,并不会因为这种违反异常说明的情况而报错。

​因此可能会出现一种情况:尽管函数说明了它不会抛出异常,但实际上还是抛出了。一旦一个noexcept函数抛出异常,程序就会调用terminate以确保遵守不在运行时抛出异常的承诺。

​上述过程是执行栈展开未作约定,因此noexcept可以用在两种情况下:一是我们确认函数不会抛出异常,二是我们根本不知道该如何处理异常。

4.2 noexcept运算符

noexcept说明符接受一个可选实参,该实参必须能转换为bool类型:如果实参是true,则函数不会抛出异常;如果实参是false,则函数可能抛出异常:

1
2
void recoup() noexcept(true);	/** 不会抛出异常 */
void alloc() noexcept(false); /** 可能抛出异常 */

noexcept说明符的实参常常与noexcept运算符混合使用。noexcept运算符是一个一元运算符,它的返回值是一个bool类型的右值常量表达式,用于表示给定的表达式是否会抛出异常。和sizeof类似,noexcept也不会求其运算对象的值。

1
2
noexcept(recoup())	/** 如果recoup不跑出异常则结果为true;否则结果为false */
noexcept(e) /** 等价于上一句 */

我们可以使用noexcept运算符得到如下的异常说明:

1
void f() noexcept(noexcept(g()));	// f 和 g的异常说明一致

如果函数g()承诺了不会抛出异常,则f也不会抛出异常;如果g()没有异常说明符,或者g虽然有异常说明符但是允许抛出异常,则f()也可能抛出异常。

noexcept有两层含义:当跟在函数参数列表后面时它是异常说明符;而当作为noexcept异常说明的bool实参出现时,它是一个运算符。

4.3 异常说明与指针、虚函数和拷贝控制

函数指针及该指针所指的函数必须具有一致的异常说明。也就是说我们为某个指针做了不抛出异常的声明,则该指针将只能指向不抛出异常的函数。相反,如果我们显式或隐式地说明了指针可能抛出异常,则该指针可以指向任何函数,即使是承诺了不抛出异常的函数也可以。

​如果虚函数承诺了它不会抛出异常,则后续派生出来的虚函数也必须做出同样的承诺;与之相反如果基类的虚函数允许抛出异常,则派生类的对应函数既可以允许抛出异常,也可以不允许抛出异常。

​当编译器合成拷贝控制成员时,同时也生成一个异常说明。如果对所有成员基类的所有操作都承诺了不会抛出异常,则合成的成员是noexcept的。如果合成成员调用的任意一个函数可能抛出异常,则合成的成员是noexcept(false)。而且如果我们定义了一个析构函数但是没有为它提供异常说明,则编译器将合成一个。合成的异常说明将于假设有编译器为类合成析构函数时所得的异常说明一致。

三路比较符(C++20)

官网解释

微软技术博客介绍

When do you actually use <=>?

例子1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <compare>

struct Rational_2 {
int num;
int den; // > 0
};

constexpr std::weak_ordering operator<=>(Rational_2 lhs, Rational_2 rhs)
{
return lhs.num * rhs.den <=> rhs.num * lhs.den;
}

void print(std::weak_ordering value)
{
if (value == 0)
std::cout << "equal\n";
else if (value < 0)
std::cout << "less\n";
else
std::cout << "greater\n";
}

int main()
{
Rational_2 c{6,5};
Rational_2 d{8,7};
print(c <=> d);
print(std::compare_three_way{}(c,d));
}

例子2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <compare>
struct Basics {
int i;
char c;
float f;
double d;
auto operator<=>(const Basics&) const = default;
};

struct Arrays {
int ai[1];
char ac[2];
float af[3];
double ad[2][2];
auto operator<=>(const Arrays&) const = default;
};

struct Bases : Basics, Arrays {
auto operator<=>(const Bases&) const = default;
};

int main() {
constexpr Bases a = { { 0, 'c', 1.f, 1. },
{ { 1 }, { 'a', 'b' }, { 1.f, 2.f, 3.f }, { { 1., 2. }, { 3., 4. } } } };
constexpr Bases b = { { 0, 'c', 1.f, 1. },
{ { 1 }, { 'a', 'b' }, { 1.f, 2.f, 3.f }, { { 1., 2. }, { 3., 4. } } } };
static_assert(a == b);
static_assert(!(a != b));
static_assert(!(a < b));
static_assert(a <= b);
static_assert(!(a > b));
static_assert(a >= b);
}

严格弱序(strict weak ordering)

关联式容器(setmultisetmapmultimap)的排序准则的定义,和std::sort的排序准则定义必须遵守严格弱序,详细描述见官方解释(strict weak ordering.pdf)。

严格弱序的定义

简单的来说就是a<b返回true,a=b和a>b返回false。

详细定义:

  1. 必须是非对称的(antisymmetric)。

    operator< 而言, 如果x < y为true, 则y < x为false。

    对判断式(predicate) op()而言,如果op(x, y)为true,则op(y, x)为false。

  2. 必须是可传递的(transitive)。

operator< 而言,如果x < y 为true且y < z为true, 则x < z 为false。

对判断式(predicate) op()而言,如果op(x, y)为true且op(y, z)为tru,则op(x, z)为true。

  1. 必须是非自反的(irreflexive)

    operator< 而言,x < x 永远是false

    对判断式(predicate) op()而言,op(x, x)永远是false。

  2. 必须有等效传递性(transitivity of equivalence)

operator< 而言,假如 !(a<b) && !(b<a) 为true且 !(b<c) && !(c<b) 为 true
那么!(a<c) && !(c<a) 也为true.
对判断式(predicate) op()而言, 假如 op(a,b), op(b,a), op(b,c), 和op(c,b) 都为
false, 那么op(a,c) and op(c,a) 也为false.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 一个定义std::set<struct>的例子
#include <set>
#include <iostream>

struct ORDERING_EXAMPLE
{
int x;
int y;
int z;

/// 重载遵循严格弱序的运算符<
bool operator < (const ORDERING_EXAMPLE& OtherStruct) const
{
if (this->x < OtherStruct.x)
return true;
if (OtherStruct.x < this->x)
return false;

// x == x则比较y
if (this->y < OtherStruct.y)
return true;
if (OtherStruct.y < this->y)
return false;

// y == y则比较z
if (this->z < OtherStruct.z)
return true;

return false;
}
};

int main()
{
std::set<ORDERING_EXAMPLE> setOrderingExample;

ORDERING_EXAMPLE stOrderingExample0 = { 0, 0, 0 };
ORDERING_EXAMPLE stOrderingExample1 = { 0, 1, 2 };
ORDERING_EXAMPLE stOrderingExample2 = { 0, 1, 3 };
ORDERING_EXAMPLE stOrderingExample3 = { 0, 1, 3 };

setOrderingExample.insert(stOrderingExample0);
setOrderingExample.insert(stOrderingExample1);
setOrderingExample.insert(stOrderingExample2);
setOrderingExample.insert(stOrderingExample3);

return 0;
}

下面举一个会崩溃的例子对二维数组排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <algorithm>
#include <iostream>
#include <vector>

int main() {
std::vector<int> temp(5, 1);
std::vector<std::vector<int>> vec(5, temp);
std::sort(vec.begin(), vec.end(),
[](const std::vector<int> &l, const std::vector<int> &r) {
if (l.size() == r.size()) {
for (size_t i = 0; i < l.size(); ++i) {
if (l.at(i) == r.at(i)) {
continue;
} else {
return l.at(i) < r.at(i);
}
}
return true; /// 这里会崩溃,改为false则不会而不会崩溃(遵循严格弱序)
} else {
return l.size() < r.size();
}
});
}

两个参数的重载符号简单示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class key
{
public:
int x;
int y;

bool operator<(const key& stOther)
{
if (x < stOther.x)
{
return true;
}
else if (x > stOther.x)
{
return false;
}
else if (y < stOther.y)
{
return true;
}
else if (y > stOther.y)
{
return false;
}
else
{
return false;
}
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class key
{
public:
int x;
std::string y;

bool operator<(const key& stOther)
{
if (x < stOther.x)
{
return true;
}
else if (x > stOther.x)
{
return false;
}
else if (y < stOther.y)
{
return true;
}
else if (y > stOther.y)
{
return false;
}
else
{
return false;
}
}
};

概述

首先我们对于乘法溢出的判断,先写测试用例:

1592715337389

由上图我们简化测试用例:

1592715602260

我们可以这样设计乘法溢出函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/// 判断两入参相乘是否溢出,溢出返回true,否则返回false
bool is_multi_overflow(int x, int y) {
if (x > 0 && y > 0) {
/// 同为正号
return x > INT_MAX/y;
} else if (x < 0 && y < 0) {
/// 同为负号
if (y == INT_MIN && x <= -1) {
return true;
}
return x < INT_MIN/-y;
} else if (x > 0 && y<0 || (x < 0 && y > 0)) {
/// 异号的情况稍等补上
return false;
} else {
return false;
}
}

接下来我们添加测试用例

1592727579022

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <cassert>
#include <iostream>
#include <numeric>

/// 判断两入参相乘是否溢出,溢出返回true,否则返回false
bool is_multi_overflow(int x, int y) {
if (x > 0 && y > 0) {
/// 同为正号
return x > INT_MAX/y;
} else if (x < 0 && y < 0) {
/// 同为负号
if (y == INT_MIN && x <= -1) {
return true;
}
return x < INT_MIN/-y;
} else if (x > 0 && y<0 || (x < 0 && y > 0)) {
/// 异号的情况稍等补上
return false;
} else {
return false;
}
}

int main() {
int x = 0;
int y = 0;
int max_num = std::numeric_limits<int>::max();
int min_num = std::numeric_limits<int>::min();

/// case 1 #1
x = 7;
y = 1 + max_num / x;
assert(is_multi_overflow(x, y));
x = max_num - 1;
y = 1 + max_num / x;
assert(is_multi_overflow(x, y));

/// case 1 #2
x = 7;
y = max_num / x;
assert(!is_multi_overflow(x, y));
x = max_num - 1;
y = max_num / x;
assert(!is_multi_overflow(x, y));

/// case 2 #1
x = -7;
y = -1 + min_num / -x;
assert(is_multi_overflow(x, y));
x = min_num + 1;
y = -1 + min_num / -x;
assert(is_multi_overflow(x, y));

/// case 2 #2
x = -7;
y = min_num / -x;
assert(!is_multi_overflow(x, y));
x = min_num + 1;
y = min_num / -x;
assert(!is_multi_overflow(x, y));
}

接下来为特殊数值来添加判断:

1592727736048

添加异号情况的判断:

1592730284047

把函数改为模板,一并添加测试用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#include <cassert>
#include <iostream>
#include <numeric>

/// 判断两入参相乘是否溢出,溢出返回true,否则返回false
template <typename T1, typename T2>
bool is_multi_overflow(T1 x, T2 y) {
static_assert(std::is_same<T1, T2>::value,
"is_multi_overflow need same type!");
static_assert(std::is_integral<T1>::value,
" is_multi_overflow need integral type!");
int num_max = std::numeric_limits<T1>::max();
int num_min = std::numeric_limits<T1>::min();
if (x == 0 || y == 0 || x == 1 || y == 1) {
return false;
}
if (x == -1) {
return y == num_min;
} else if (y == -1) {
return x == num_min;
}

if (x > 0 && y > 0) {
/// 同为正号
return x > num_max / y;
} else if (x < 0 && y < 0) {
/// 同为负号
if (y == num_min && x <= -1) {
return true;
}
return x < num_min / -y;
} else if (x > 0 && y < 0 || (x < 0 && y > 0)) {
/// 异号的情况
if (x > y) {
std::swap(x, y);
}
return x < num_min / y;
} else {
return false;
}
}

int main() {
int x = 0;
int y = 0;
int max_num = std::numeric_limits<int>::max();
int min_num = std::numeric_limits<int>::min();

/// case 1 #1
x = 7;
y = 1 + max_num / x;
assert(is_multi_overflow(x, y));
x = max_num - 1;
y = 1 + max_num / x;
assert(is_multi_overflow(x, y));

/// case 1 #2
x = 7;
y = max_num / x;
assert(!is_multi_overflow(x, y));
x = max_num - 1;
y = max_num / x;
assert(!is_multi_overflow(x, y));

/// case 2 #1
x = -7;
y = -1 + min_num / -x;
assert(is_multi_overflow(x, y));
x = min_num + 1;
y = -1 + min_num / -x;
assert(is_multi_overflow(x, y));

/// case 2 #2
x = -7;
y = min_num / -x;
assert(!is_multi_overflow(x, y));
x = min_num + 1;
y = min_num / -x;
assert(!is_multi_overflow(x, y));

/// case 3
x = 0;
y = max_num;
assert(!is_multi_overflow(x, y));
x = min_num;
y = 0;
assert(!is_multi_overflow(x, y));
x = 0;
y = 0;
assert(!is_multi_overflow(x, y));

/// case 4
x = 1;
y = max_num;
assert(!is_multi_overflow(x, y));
x = INT_MIN;
y = 1;
assert(!is_multi_overflow(x, y));

/// case 5
x = -1;
y = max_num;
assert(!is_multi_overflow(x, y));
x = -1;
y = min_num;
assert(is_multi_overflow(x, y));

/// case 6
x = 2;
y = min_num / 2;
assert(!is_multi_overflow(x, y));
assert(!is_multi_overflow(y, x));
x = 2;
y = -1 + min_num / 2;
assert(is_multi_overflow(x, y));
assert(is_multi_overflow(y, x));
}

最后附上完整测试用例:

1592730393076

后记

我们既然有了判断乘法溢出的函数,我们可以借此封装一个带有检查溢出的乘法函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <optional>

template <typename T1, typename T2>
std::optional<T1> multiplies_s(const T1 x, const T2 y) noexcept {
static_assert(std::is_same<T1, T2>::value, "Multiplies_s need same type!");
static_assert(std::is_integral<T1>::value,
"Multiplies_s need integral type!");
if (is_multi_overflow(x, y)) return {};
return x * y;
}

int main()
{
int x = 5;
int y = 5;
int result = multiplies_s(x, y).value_or(0);
}

虚析构函数问题

引用标准中原文: 一条有用的方针,是任何基类的析构函数必须为公开且虚, 或受保护且非虚。

虚析构这个概念被设计出来就是为了解决基类指针指向派生类实例的析构问题,当一个基类指针指向派生类实例然后进行delete该指针时,只会执行基类析构函数而派生类的析构函数不会被执行,这将导致派生类构造的资源不会被正确释放,造成内存泄漏。如下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>

struct Base
{
Base() { std::cout << "Base Construct!" << std::endl; }
/// 该析构函数为错误示例,严禁这样写.
~Base() { std::cout << "Base Deconstruct!" << std::endl; }
};

struct Derived : public Base
{
Derived() { std::cout << "Derived Construct!" << std::endl; }
~Derived() { std::cout << "Derived Deconstruct!" << std::endl; }
};

int main()
{
{
/** 使用基类指针指向派生类实例 */
Base* BasePtr = new Derived;
delete BasePtr;
}
system("pause");
}

运行结果:

Virtual DeConstruct Debug

可以看到派生类没有被析构,如要解决该问题在基类析构函数处加上virtual关键字即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>

struct Base
{
Base() { std::cout << "Base Construct!" << std::endl; }
/** 正确写法: 加上关键字virtual, 后面函数体可写可不写,或者直接使用=default都行。 */
virtual ~Base() { std::cout << "Base Deconstruct!" << std::endl; }
};

struct Derived : public Base
{
Derived() { std::cout << "Derived Construct!" << std::endl; }
~Derived() { std::cout << "Derived Deconstruct!" << std::endl; }
/// 或者 virtual ~Derived() override {}
};

int main()
{
{
/** 使用基类指针指向派生类实例 */
Base* BasePtr = new Derived;
delete BasePtr;
}
system("pause");
}

运行结果:

Virtual DeConstruct Debug Correct

PlantUML语法学习

类图

类之间的关系

类之间的关系通过下面的符号定义:

类之间的关系

1
2
3
4
5
6
7
@startuml
Class01 <|-- Class02
Class03 *-- Class04
Class05 o-- Class06
Class07 .. Class08
Class09 -- Class10
@enduml

class1

1
2
3
4
5
6
7
@startuml
Class11 <|.. Class12
Class13 --> Class14
Class15 ..> Class16
Class17 ..|> Class18
Class19 <--* Class20
@enduml

class2

1
2
3
4
5
6
7
@startuml
Class21 #-- Class22
Class23 x-- Class24
Class25 }-- Class26
Class27 +-- Class28
Class29 ^-- Class30
@enduml

class3

关系上的标识

在关系之间使用标签来说明时, 使用: 后接标签文字。
对元素的说明,你可以在每一边使用"" 来说明.

1
2
3
4
5
@startuml
Class01 "1" *-- "many" Class02 : contains
Class03 o-- Class04 : aggregation
Class05 --> "1" Class06
@enduml

class4

在标签的开始或结束位置添加< 或> 以表明是哪个对象作用到哪个对象上。

1
2
3
4
5
6
@startuml
class Car
Driver - Car : drives >
Car *- Wheel : have 4 >
Car -- Person : < owns
@enduml

class5

添加方法

为了声明字段(对象属性)或者方法,你可以使用后接字段名或方法名。
系统检查是否有括号来判断是方法还是字段。

1
2
3
4
5
6
@startuml
Object <|-- ArrayList
Object : equals()
ArrayList : Object[] elementData
ArrayList : size()
@enduml

class6

也可以使用{} 把字段或者方法括起来
注意,这种语法对于类型/名字的顺序是非常灵活的。

1
2
3
4
5
6
7
8
9
10
@startuml
class Dummy {
String data
void methods()
}
class Flight {
flightNumber : Integer
departureTime : Date
}
@enduml

class7

你可以(显式地)使用{field} 和{method} 修饰符来覆盖解析器的对于字段和方法的默认行为

1
2
3
4
5
6
@startuml
class Dummy {
{field} A field (despite parentheses)
{method} Some method
}
@enduml

class8

定义可访问性

一旦你定义了域或者方法,你可以定义相应条目的可访问性质。
定义可访问性

1
2
3
4
5
6
7
8
@startuml
class Dummy {
-field1
#field2
~method1()
+method2()
}
@enduml

class9

你可以采用命令(skinparam classAttributeIconSize 0 :)停用该特性

1
2
3
4
5
6
7
8
9
@startuml
skinparam classAttributeIconSize 0
class Dummy {
-field1
#field2
~method1()
+method2()
}
@enduml

class10

抽象与静态

通过修饰符{static} 或者{abstract},可以定义静态或者抽象的方法或者属性。
这些修饰符可以写在行的开始或者结束。也可以使用{classifier} 这个修饰符来代替{static}.

1
2
3
4
5
6
@startuml
class Dummy {
{static} String id
{abstract} void methods()
}
@enduml

class11

高级类体

PlantUML 默认自动将方法和属性重新分组,你可以自己定义分隔符来重排方法和属性,下面的分隔符都
是可用的:-- … == __.
还可以在分隔符中添加标题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@startuml
class Foo1 {
You can use
several lines
..
as you want
and group
==
things together.
__
You can have as many groups
as you want
--
End of class
}
class User {
.. Simple Getter ..
+ getName()
+ getAddress()
.. Some setter ..
+ setName()
__ private data __
int age
-- encrypted --
String password
}
@enduml

class12

更多注释

可以在注释中使用部分html 标签:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@startuml
class Foo
note left: On last defined class
note top of Object
In java, <size:18>every</size> <u>class</u>
<b>extends</b>
<i>this</i> one.
end note
note as N1
This note is <u>also</u>
<b><color:royalBlue>on several</color>
<s>words</s> lines
And this is hosted by <img:sourceforge.jpg>
end note
@enduml

class13

对象图

对象的定义

使用关键字object 定义实例。

1
2
3
4
@startuml
object firstObject
object "My Second Object" as o2
@enduml

如下图生成:

object1

对象之间的关系

对象之间的关系可以用如下符号定义:

对象之间的关系

也可以用… 来代替-- 以使用点线。
知道了这些规则,就可以画下面的图:
可以用冒号给关系添加标签,标签内容紧跟在冒号之后。
用双引号在关系的两边添加基数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@startuml
object Object01
object Object02
object Object03
object Object04
object Object05
object Object06
object Object07
object Object08
Object01 <|-- Object02
Object03 *-- Object04
Object05 o-- "4" Object06
Object07 .. Object08 : some labels
@enduml

如下图生成:

object2

添加属性

用冒号加属性名的形式声明属性。

1
2
3
4
5
@startuml
object user
user : name = "Dummy"
user : id = 123
@enduml

如下图生成:

object3

活动图

简单活动

使用(*) 作为活动图的开始点和结束点。
有时,你可能想用(*top) 强制开始点位于图示的顶端。
使用–> 绘制箭头。

1
2
3
4
@startuml
(*) --> "First Activity"
"First Activity" --> (*)
@enduml

activity1

箭头上的标签

默认情况下,箭头开始于最接近的活动。
可以用[ 和 ] 放在箭头定义的后面来添加标签。

1
2
3
4
5
@startuml
(*) --> "First Activity"
-->[You can put also labels] "Second Activity"
--> (*)
@enduml

activity2

改变箭头方向

你可以使用-> 定义水平方向箭头,还可以使用下列语法强制指定箭头的方向:

  • -down-> (default arrow)
  • -right-> or ->
  • -left->
  • -up->

activity3

分支

你可以使用关键字if/then/else 创建分支。

1
2
3
4
5
6
7
8
9
10
11
@startuml
(*) --> "Initialization"
if "Some Test" then
-->[true] "Some Activity"
--> "Another activity"
-right-> (*)
else
->[false] "Something else"
-->[Ending process] (*)
endif
@enduml

activity4

不过,有时你可能需要重复定义同一个活动:

1
2
3
4
5
6
7
8
9
10
@startuml
(*) --> "check input"
If "input is verbose" then
--> [Yes] "turn on verbosity"
--> "run command"
else
--> "run command"
Endif
-->(*)
@enduml

activity5

更多分支

默认情况下,一个分支连接上一个最新的活动,但是也可以使用if 关键字进行连接。
还可以嵌套定义分支。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@startuml
(*) --> if "Some Test" then
-->[true] "activity 1"
if "" then
-> "activity 3" as a3
else
if "Other test" then
-left-> "activity 5"
else
--> "activity 6"
endif
endif
else
->[false] "activity 2"
endif
a3 --> if "last test" then
--> "activity 7"
else
-> "activity 8"
endif
@enduml

activity6

注释

你可以在活动定义之后用note left, note right, note top or note bottom, 命令给活动添加注释。
如果想给开始点添加注释,只需把注释的定义放在活动图最开始的地方即可。
也可以用关键字endnote 定义多行注释。

1
2
3
4
5
6
7
8
9
@startuml
(*) --> "Some Activity"
note right: This activity has to be defined
"Some Activity" --> (*)
note left
This note is on
several lines
end note
@enduml

activity7

分区

用关键字partition 定义分区,还可以设置背景色(用颜色名或者颜色值)。
定义活动的时候,它自动被放置到最新的分区中。
用} 结束分区的定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@startuml
partition Conductor {
(*) --> "Climbs on Platform"
--> === S1 ===
--> Bows
}
partition Audience #LightSkyBlue {
=== S1 === --> Applauds
}
partition Conductor {
Bows --> === S2 ===
--> WavesArmes
Applauds --> === S2 ===
}
partition Orchestra #CCCCEE {
WavesArmes --> Introduction
--> "Play music"
}
@enduml

activity8

一个完整的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@startuml
title Servlet Container
(*) --> "ClickServlet.handleRequest()"
--> "new Page"
if "Page.onSecurityCheck" then
->[true] "Page.onInit()"
if "isForward?" then
->[no] "Process controls"
if "continue processing?" then
-->[yes] ===RENDERING===
else
-->[no] ===REDIRECT_CHECK===
endif
else
-->[yes] ===RENDERING===
endif
if "is Post?" then
-->[yes] "Page.onPost()"
--> "Page.onRender()" as render
--> ===REDIRECT_CHECK===
else
-->[no] "Page.onGet()"
--> render
endif
else
-->[false] ===REDIRECT_CHECK===
endif
if "Do redirect?" then
->[yes] "redirect request"
--> ==BEFORE_DESTROY===
else
if "Do Forward?" then
-left->[yes] "Forward request"
--> ==BEFORE_DESTROY===
else
-right->[no] "Render page template"
--> ==BEFORE_DESTROY===
endif
endif
--> "Page.onDestroy()"
-->(*)
@enduml

activity9

界面格式相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@startuml
skinparam SequenceGroupBodyBackgroundColor #FFFFFF90

box "Internal Service" #LightBlue
participant Bob
participant Alice
end box

box "Other" #LightGreen
participant Other
end box

group group
Bob -> Alice : hello
Alice -> Other : hello
end
@enduml

skinparam_uml_0

颜色示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
@startuml

!unquoted procedure $DrawColor($colour)

skinparam rectangle {
backgroundColor<<$colour>> $colour
borderColor<<$colour>> $colour
shadowing<<$colour>> true
BorderThickness<<$colour>> 1
}

rectangle $colour <<$colour>> as "<color:$colour></color>"

!endprocedure

package HexCodes {
$DrawColor("00ff00")
$DrawColor("ff0000")
$DrawColor("0000ff")
$DrawColor("123456")
$DrawColor("654321")
$DrawColor("165432")
$DrawColor("ff22ff")
}

package Colours {
$DrawColor("APPLICATION")
$DrawColor("AliceBlue")
$DrawColor("AntiqueWhite")
$DrawColor("Aqua")
$DrawColor("Aquamarine")
$DrawColor("Azure")
$DrawColor("BUSINESS")
$DrawColor("Beige")
$DrawColor("Bisque")
$DrawColor("Black")
$DrawColor("BlanchedAlmond")
$DrawColor("Blue")
$DrawColor("BlueViolet")
$DrawColor("Brown")
$DrawColor("BurlyWood")
$DrawColor("CadetBlue")
$DrawColor("Chartreuse")
$DrawColor("Chocolate")
$DrawColor("Coral")
$DrawColor("CornflowerBlue")
$DrawColor("Cornsilk")
$DrawColor("Crimson")
$DrawColor("Cyan")
$DrawColor("DarkBlue")
$DrawColor("DarkCyan")
$DrawColor("DarkGoldenRod")
$DrawColor("DarkGray")
$DrawColor("DarkGreen")
$DrawColor("DarkGrey")
$DrawColor("DarkKhaki")
$DrawColor("DarkMagenta")
$DrawColor("DarkOliveGreen")
$DrawColor("DarkOrchid")
$DrawColor("DarkRed")
$DrawColor("DarkSalmon")
$DrawColor("DarkSeaGreen")
$DrawColor("DarkSlateBlue")
$DrawColor("DarkSlateGray")
$DrawColor("DarkSlateGrey")
$DrawColor("DarkTurquoise")
$DrawColor("DarkViolet")
$DrawColor("Darkorange")
$DrawColor("DeepPink")
$DrawColor("DeepSkyBlue")
$DrawColor("DimGray")
$DrawColor("DimGrey")
$DrawColor("DodgerBlue")
$DrawColor("FireBrick")
$DrawColor("FloralWhite")
$DrawColor("ForestGreen")
$DrawColor("Fuchsia")
$DrawColor("Gainsboro")
$DrawColor("GhostWhite")
$DrawColor("Gold")
$DrawColor("GoldenRod")
$DrawColor("Gray")
$DrawColor("Green")
$DrawColor("GreenYellow")
$DrawColor("Grey")
$DrawColor("HoneyDew")
$DrawColor("HotPink")
$DrawColor("IMPLEMENTATION")
$DrawColor("IndianRed")
$DrawColor("Indigo")
$DrawColor("Ivory")
$DrawColor("Khaki")
$DrawColor("Lavender")
$DrawColor("LavenderBlush")
$DrawColor("LawnGreen")
$DrawColor("LemonChiffon")
$DrawColor("LightBlue")
$DrawColor("LightCoral")
$DrawColor("LightCyan")
$DrawColor("LightGoldenRodYellow")
$DrawColor("LightGray")
$DrawColor("LightGreen")
$DrawColor("LightGrey")
$DrawColor("LightPink")
$DrawColor("LightSalmon")
$DrawColor("LightSeaGreen")
$DrawColor("LightSkyBlue")
$DrawColor("LightSlateGray")
$DrawColor("LightSlateGrey")
$DrawColor("LightSteelBlue")
$DrawColor("LightYellow")
$DrawColor("Lime")
$DrawColor("LimeGreen")
$DrawColor("Linen")
$DrawColor("MOTIVATION")
$DrawColor("Magenta")
$DrawColor("Maroon")
$DrawColor("MediumAquaMarine")
$DrawColor("MediumBlue")
$DrawColor("MediumOrchid")
$DrawColor("MediumPurple")
$DrawColor("MediumSeaGreen")
$DrawColor("MediumSlateBlue")
$DrawColor("MediumSpringGreen")
$DrawColor("MediumTurquoise")
$DrawColor("MediumVioletRed")
$DrawColor("MidnightBlue")
$DrawColor("MintCream")
$DrawColor("MistyRose")
$DrawColor("Moccasin")
$DrawColor("NavajoWhite")
$DrawColor("Navy")
$DrawColor("OldLace")
$DrawColor("Olive")
$DrawColor("OliveDrab")
$DrawColor("Orange")
$DrawColor("OrangeRed")
$DrawColor("Orchid")
$DrawColor("PHYSICAL")
$DrawColor("PaleGoldenRod")
$DrawColor("PaleGreen")
$DrawColor("PaleTurquoise")
$DrawColor("PaleVioletRed")
$DrawColor("PapayaWhip")
$DrawColor("PeachPuff")
$DrawColor("Peru")
$DrawColor("Pink")
$DrawColor("Plum")
$DrawColor("PowderBlue")
$DrawColor("Purple")
$DrawColor("Red")
$DrawColor("RosyBrown")
$DrawColor("RoyalBlue")
$DrawColor("STRATEGY")
$DrawColor("SaddleBrown")
$DrawColor("Salmon")
$DrawColor("SandyBrown")
$DrawColor("SeaGreen")
$DrawColor("SeaShell")
$DrawColor("Sienna")
$DrawColor("Silver")
$DrawColor("SkyBlue")
$DrawColor("SlateBlue")
$DrawColor("SlateGray")
$DrawColor("SlateGrey")
$DrawColor("Snow")
$DrawColor("SpringGreen")
$DrawColor("SteelBlue")
$DrawColor("TECHNOLOGY")
$DrawColor("Tan")
$DrawColor("Teal")
$DrawColor("Thistle")
$DrawColor("Tomato")
$DrawColor("Turquoise")
$DrawColor("Violet")
$DrawColor("Wheat")
$DrawColor("White")
$DrawColor("WhiteSmoke")
$DrawColor("Yellow")
$DrawColor("YellowGreen")

}
@enduml

color_uml

附注

PlantUML资料

progschj/thread_pool

Github上这个库(progschj/thread_pool)的点赞最多,学习一下。

接口定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ThreadPool {
public:
ThreadPool(size_t);
/// 任务入列
template <class F, class... Args>
auto enqueue(F&& f, Args&&... args)
-> std::future<typename std::invoke_result<F(Args...)>::type>;
~ThreadPool();

private:
/// 所有的工作线程
std::vector<std::thread> workers;
/// 任务队列
std::queue<std::function<void()> > tasks;

/// 用于同步的互斥锁和条件变量
std::mutex queue_mutex;
std::condition_variable condition;
bool stop; ///< 用于判断所有线程是否需要结束
};

构造函数和消费者实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/// @name     ThreadPool
/// @brief 用于创建若干个线程,并规定消费者函数
///
/// @param threads [in] 要创建的线程数量
///
/// @return NONE
///
/// @date 2020-06-27 16:17:50
/// @warning 线程安全
inline ThreadPool::ThreadPool(size_t threads) : stop(false) {
for (size_t i = 0; i < threads; ++i)
workers.emplace_back([this] {
for (;;) {
std::function<void()> task;

{
/// 获取同步锁
std::unique_lock<std::mutex> lock(this->queue_mutex);
/// 阻塞等待获取任务,直到任务队列不为空
this->condition.wait(
lock, [this] { return this->stop || !this->tasks.empty(); });
/// 如果stop标志位为true,且任务列表都执行完毕后,该线程退出
if (this->stop && this->tasks.empty()) return;
/// 从任务队列中拿出来一个任务
task = std::move(this->tasks.front());
this->tasks.pop();
} ///< 这里释放锁

/// 执行该任务函数
task();
}
});
}

析构函数

1
2
3
4
5
6
7
8
9
inline ThreadPool::~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
/// 在stop位, 置为true后通知所有线程执行一次,然后等待所有线程处理完任务后join()
condition.notify_all();
for (std::thread& worker : workers) worker.join();
}

生产者函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/// @name     enqueue
/// @brief 用于添加任务函数到任务队列中
///
/// @param f [in] 任务函数
/// @param args [in] 任务函数的入参列表
///
/// @return 取决于任务函数的返回值
///
/// @date 2020-06-27 16:06:30
/// @warning 线程安全
template <class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args)
-> std::future<typename std::invoke_result<F(Args...)>::type> {
using return_type = typename std::invoke_result<F(Args...)>::type;

/// 这里封装一个异步的线程并执行刚刚传入的函数,这个函数通过bind改类型为void()
auto task = std::make_shared<std::packaged_task<return_type()> >(
std::bind(std::forward<F>(f), std::forward<Args>(args)...));
/// 创建一个这个函数的未来的值, 这个未来值不获取就不会进行计算
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex);

/// 假如说没有让这个线程停止则继续,否则抛出异常阻止线程池结束后在入列
if (stop) throw std::runtime_error("enqueue on stopped ThreadPool");
/// 这个封装好的函数放入任务列表中
tasks.emplace([task]() { (*task)(); });
}
/// 通知一个阻塞中的线程,任务队列中有任务了
condition.notify_one();
return res;
}

std::async介绍

下面是一个很好的并行计算的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <future>
#include <iostream>
#include <string>

bool is_prime(int x)
{
for (int i = 2; i < x; i++)
{
if (x % i == 0)
return false;
}
return true;
}

int main()
{
/** is_prime(700020007)这个函数调用隐藏于主线程,异步执行 */
std::future<bool> fut = std::async(is_prime, 700020007);
std::cout << "please wait";
std::chrono::milliseconds span(100);
/** 这个异步调用函数等待100ms,如果没有计算完就继续等待 */
while (fut.wait_for(span) != std::future_status::ready)
std::cout << ".";
std::cout << std::endl;

/** 计算完毕后,获取函数返回值 */
std::cout << "final result: " << std::boolalpha << fut.get() << std::endl;

system("pause");
return 0;
}

std::async中的第一个参数是启动策略,它控制std::async的异步行为,我们可以用三种不同的启动策略来创建std::async
·std::launch::async
保证异步行为,即传递函数将在单独的线程中执行
·std::launch::deferred
当其他线程调用get()来访问共享状态时,将调用非异步行为
·std::launch::async | std::launch::deferred
默认行为。有了这个启动策略,它可以异步运行或不运行,这取决于系统的负载,但我们无法控制它。

见下面例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <iostream>
#include <string>
#include <chrono>
#include <thread>
#include <future>

using namespace std::chrono;

std::string fetchDataFromDB(std::string recvData)
{
//确保函数要5秒才能执行完成
std::this_thread::sleep_for(seconds(5));

//处理创建数据库连接、获取数据等事情
return "DB_" + recvData;
}

std::string fetchDataFromFile(std::string recvData)
{
//确保函数要5秒才能执行完成
std::this_thread::sleep_for(seconds(5));

//处理获取文件数据
return "File_" + recvData;
}

int main()
{
//获取开始时间
system_clock::time_point start = system_clock::now();

/** 使用std::launch::async,来指定其异步执行 */
std::future<std::string> resultFromDB = std::async(std::launch::async,
fetchDataFromDB, "Data");

//从文件获取数据
std::string fileData = fetchDataFromFile("Data");

//从DB获取数据
//数据在future<std::string>对象中可获取之前,将一直阻塞
std::string dbData = resultFromDB.get();

//获取结束时间
auto end = system_clock::now();

auto diff = duration_cast<std::chrono::seconds>(end - start).count();
std::cout << "Total Time taken= " << diff << "Seconds" << std::endl;

//组装数据
std::string data = dbData + " :: " + fileData;

//输出组装的数据
std::cout << "Data = " << data << std::endl;

return 0;
}

std::promise介绍

std::promise的作用就是提供一个不同线程之间的数据同步机制,它可以存储一个某种类型的值,并将其传递给对应的future, 即使这个future不在同一个线程中也可以安全的访问到这个值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>       // std::cout
#include <functional> // std::ref
#include <thread> // std::thread
#include <future> // std::promise, std::future

void print_int (std::future<int>& fut)
{
std::cout << "Enter print_int: " << std::endl;
int x = fut.get(); ///< 在这里会等待外部std::promise变量set_value进来,否则会一致阻塞在这里
std::cout << "value: " << x << '\n';
}

int main ()
{
std::promise<int> prom; // 创建一个std::promise变量

std::future<int> fut = prom.get_future(); // 创建一个std::future变量

std::thread th1 (print_int, std::ref(fut)); // 创建一个线程执行函数print_int

std::this_thread::sleep_for(std::chrono::seconds(3));
prom.set_value (10); // 传值进入线程th1

th1.join();
system("pause");
return 0;
}

std::packaged_task介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>     // std::cout
#include <future> // std::packaged_task, std::future
#include <chrono> // std::chrono::seconds
#include <thread> // std::thread, std::this_thread::sleep_for

// count down taking a second for each value:
int countdown (int from, int to)
{
for (int i = from; i != to; --i)
{
std::cout << i << '\n';
std::this_thread::sleep_for(std::chrono::seconds(1));
}
std::cout << "Lift off!\n";
return from - to;
}

int main ()
{
// 创建一个std::packaged_task对象
std::packaged_task<int(int, int)> tsk (countdown);
// 创建一个std::future对象,用于跨线程异步获取该线程返回的值
std::future<int> ret = tsk.get_future();
// 把线程对象移动进一个可运行的线程中
std::thread th (std::move(tsk), 10, 0);
// 让该线程从主线程中分离
th.detach();
// ...
// 利用std::future对象来获取已经分离开的线程运行是否结束的返回的值
int value = ret.get();
std::cout << "The countdown lasted for " << value << " seconds.\n";

system("pause");
return 0;
}

硬件支持的线程数量

由于硬件支持的并行线程数量有限,如果创建线程的数量比硬件支持的数量要多,那么CPU进行的上下文切换可能会浪费大量时间,所以了解硬件支持的线程数量是高效并行编程的重点。

使用std::thread::hardware_concurrency()来获取硬件支持的线程数量。

1
2
3
4
5
6
7
#include <iostream>
#include <thread>

int main() {
unsigned int n = std::thread::hardware_concurrency();
std::cout << n << " concurrent threads are supported.\n";
}

std::thread::yield介绍

关于std::thread::yield 和 std::sleep_for的比较

例子:

1
2
3
4
5
6
7
8
9
10
11
12
void worker_thread() {
while (true) {
std::function<void()> task;
if (work_queue.try_pop(task)) {
/// 获取到任务就运行
task();
} else {
/// 没有获取到就休息一下
std::this_thread::yield();
}
}
}