四季

Seasons

一、值类别

1.1 值类别是什么

在 C++ 中,表达式是由一个或多个操作数(operands)和零个或多个运算符(operators)组成的语法单元,每个表达式都会生成一个值,每个值有两种属性:类型与值类别。类型有intdouble等内置类型,也有用户自定义类型。值类别是指按是否可标识与是否可移动两个独立属性对值进行分类。 C++ 语言及其工具和规则的许多方面都需要正确理解这些值类别以及对它们的引用。 这些方面包括获取值的地址、复制值、移动值、将值转发给另一函数。

1.2 C++ 98/03

C++ 98/03依据是否可标识将值类别分为左值与右值两种。这里的左与右是指是否可出现在赋值运算符的左边与右边。但这只是最简单字面意思,实际区分左值与右值是通过是否可标识

左值(lvalue):一个指向特定内存的具有标识的值(具名对象),它有一个相对稳定的内存地址,并有较长生命周期。命名的变量或常量,返回左值引用的函数都是左值。

1
2
3
4
5
6
7
8
9
10
11
12
13
int global_var = 2025; // 左值

int &get_val() // 左值,返回引用类型
{
return global_var;
}

int main()
{
int x = 10; // 左值

return 0;
}
Read more »

一、std::optional

1.1 引言

在开发中,经常会处理一些“可能为空”的函数返回值,该返回值可能赋予了一个有效值,也可能没有被赋予有效值。为了区分这两种情况,需要写一些额外的代码,导致代码可读性较差,甚至更容易出错。常见的做法有:

  • 返回一个magic value,表示“为空”,不是有效值。但它存在语义不清晰,稍不注意就会与合法值冲突。
  • 函数形参增加一个输出型参数,表示函数返回值是否被赋值。但它会导致接口隐晦,代码可读性差。

例如:有个函数查找一个值是否在数组中存在,如果存在则返回在数组中首次出现的下标。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 使用-1作为 magic value
int find_value(const std::vector<int> &vec, int val)
{
for (int i = 0; i < vec.size(); i++)
{
if (vec[i] == val) return i;
}

return -1;
}

// 使用输出型参数
int find_value(const std::vector<int> &vec, int val, bool &exists)
{
int i = 0;
exists = true;
for (; i < vec.size(); i++)
{
if (vec[i] == val) return i;
}

exists = false;
return i;
}
Read more »

一、结构化绑定

1.1 引子

在 C++中,如果一个函数需要返回两个值,我们可以使用std::pair 。例如:有个函数查找并返回一个整数数组的最小值和最大值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::pair<int, int> findMinMax(const std::vector<int> &numbers)
{
if (numbers.empty()) return {0, 0};

int min = numbers[0];
int max = numbers[0];

for (int num: numbers)
{
if (num < min) min = num;

if (num > max) max = num;
}

return {min, max};
}

我们有多种方法可以获取std::pair中的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::vector<int> data = {42, 17, 89, 3, 56, 21};

// 方法一
auto r = findMinMax(data);
int min = r.first;
int max = r.second;

// 方法二
auto r = findMinMax(data);
int min = std::get<0>(r);
int max = std::get<1>(r);


// 方法三
int min, max;
std::tie(min, max) = findMinMax(data);

我们发现虽然有多种方法可以获取std::pair中的参数,但是都有如下问题:1. 代码不够简洁 2. 可读性较差。在C++ 17中可以通过结构化绑定一行代码搞定:

1
auto [min, max] = findMinMax(data);
Read more »

  最近在写一个功能的单元测试。写完第一个子模块相关的测试代码后运行没有问题,当写完第二个子模块的测试代码后运行直接Segmentation fault崩溃了。gdb 调试core dump文件发现在一行打印语句处崩溃的,我想难不成非法访问内存了。于是又重新检查了相关代码,发现用到的变量都是局部变量,而且都是静态开辟,没理由会Segmentation fault才对。于是想着使用valgrind 工具检查下程序内存,结果一下就定位到错误了,原来是在main函数内定义了一个大数组,造成栈溢出。由于没有在定义数组那行崩溃,而是在间隔很远的打印语句那行崩溃的,所以最开始没有排查出来。

Read more »

在项目开发中我们会用到STL中各种容器,假设我们有一个 map,我们希望查找一个元素,并在找到时执行某些操作:

1
2
3
4
5
6
7
std::map<int, std::string> m = {{1, "one"}, {2, "two"}, {3, "three"}};

auto it = m.find(2);
if (it != m.end())
{
std::cout << "Found: " << it->second << std::endl;
}

上面代码虽然能够完成我们的要求,但有如下问题:

  • 作用域污染:it变量在if语句之后仍能被继续使用,会造成作用域污染。
  • 简洁性与可维护性差:代码不够简洁,并且当it迭代器失效时继续使用会有未知风险。
Read more »

一、枚举快速入门

1.1 枚举是什么

​ 枚举(enum,全称 enumeration)是 C/C++ 提供的一种用户自定义数据类型。用户定义一组具名的整数标识符(称为“枚举集”、“枚举器常量”、“枚举器”或“成员”),枚举类型的变量存储该类型所定义的枚举集的值之一。

​ 在某些场景下使用枚举更具可读性和可维护性,例如:表示状态机、定义程序错误码、位掩码等。

1.2 枚举的使用

语法:

1
2
3
4
enum [identifier]
{
enumerator-list;
};

identifier : 可选,枚举类型名,如果没有指定,则为匿名枚举

enumerator-list:定义枚举集的成员

Read more »

最近在项目中使用assert出现了一个bug,排查了半天才定位到错误,在讲这个陷阱之前先铺垫一下assert基础知识。 assert是 C/C++ 中的一个,定义在assert.h文件中。assert 宏将在 expression 计算结果为 false(0) 时打印错误信息并调用abort函数终止进程。 如果 expressiontrue (非0),则不执行任何操作。 错误信息包括失败的expression、源文件名以及失败的行号。如果定义了NDEBUG宏,assert不会被启用assert适用于捕捉不应该发生的非法情况,而不是错误处理,因为错误是可以预料的。

Read more »

前言:之前在工作中简单用过gtest,但当时项目比较赶时间,对测试并不是很重视。经常出现一个人改完代码后,模块之间出现bug情况,然后花很长时间排查bug。吸取教训后准备在新项目中引入gtest,花了两天时间学习了gtest文档,发现它功能还是蛮多的。准备整理一个gtest系列的专栏,也是检验自己学习成果。

一、 gtest快速入门

1.1 gtest是什么

gtest 是 Google Test 的简称,是一个由 Google 开发与维护的 C++ 测试框架。它提供了丰富的断言和测试工具,帮助开发者编写更好的 C++ 测试代码。

Read more »

一、概述

1.1 简介

RocksDB 是一个持久、内嵌型 K/V存储引擎,键和值是任意大小的字节流(没有类型)。它支持point lookuprange scan,并提供不同类型的 ACID 保证。它是一个 C++ 库。RocksDB 借鉴了开源leveldb项目的重要代码以及Apache HBase的设计理念。初始代码是从开源 leveldb 1.5 fork而来的。它还以 Meta 在 RocksDB 之前开发的代码和理念为基础。

RocksDB 具有高度灵活的设置,可以根据不同的生产环境进行调优,包括 SSD、硬盘、ramfs 或远程存储。它支持多种压缩算法,并提供了良好的生产支持和调试工具。另一方面,RocksDB 也努力限制可调参数的数量,提供足够好的开箱即用性能,并在适用的地方使用一些自适应算法。

Read more »

1. 缺乏控制的特殊成员函数

 C++中,如果没有显式定义某些特殊成员函数时,编译器会自动为类生成这些成员函数。例如:默认构造函数、析构函数、拷贝构造函数、赋值运算符重载函数等,这个特性让程序员可以有更多精力关注类本身的功能而不必为了某些语法特性而分心,同时也避免了让程序员编写重复的代码,比如:

Read more »
0%