忧郁的大能猫
好奇的探索者,理性的思考者,踏实的行动者。
Table of Contents:
C++ 的国际标准有 6 个版本,98, 03, 11, 14, 17,20 。
C++11则是一次全面的大进化。
C++11—之后,C++ 以每三年一版的频度发布着新的语言标准。
C++ 是一门多范式的通用编程语言,这些不同的范式,都可以在同一项目中组合使用,这就大大增加了开发的灵活性。因此,C++ 适用的领域非常广泛,小到嵌入式,大到分布式服务器,到处可以见到 C++ 的身影。
C++ 的核心竞争力:
* 抽象能力:意味着较高的开发效率,同时,更重要的是,不会因抽象而降低性能。
* 性能:这不用多说了,就是快并且占用资源少。
* 功耗:这是近年来我们越来越关注的问题,跟性能直接相关,性能好了功耗自然就低。移动设备上。
* C语言的兼容性,也是 C++ 的一大优势
目前,跟 C++ 定位差不多、能有直接竞争关系的,也就是既支持高度抽象、又追求高性能的通用编程语言,其实只有 Rust 一种。
我的个人经验,完成同样的功能,C++ 需要的代码行数一般是 Python 的三倍左右,而性能则可以达到Python 的十倍以上。
当你的软件属于运算密集或者内存密集型,你需要性能、且愿意为性能付出额外代价的时候,应该考虑用 C++,特别在你的代码需要部署在多台服务器或者移动设备的场合。反之,如果性能不会成为你开发的软件的瓶颈,那 C++ 可能就不是一个最合适的工具。
对于 CentOS 7,系统安装的 GCC 版本是 4.8,太老,你可以通过安装 centos-
release-scl 和 devtoolset-7-gcc-c++ 两个包来获得 GCC 7;随后,可以使用命令 scl
enable devtoolset-7 bash 或 . /opt/rh/devtoolset-7/enable 来启用 GCC7
可以使用ubuntu上的版本更加新
即使不用较新的 C++ 特性,你也一定要用比较新的编译器。单单是输出错误信息的友好程度,老版本和新版本就是没法比的。
在 C++ 里,所有的变量缺省都是值语义——如果不使用 * 和 & 的话,变量不会像 Java 或 Python 一样引用一个堆上的对象。
例子:
void foo()
{
bar* ptr = new bar();
…
delete ptr;
}
存在两个问题:
1. 省略的代码部分也许会抛出异常,导致最后的 delete ptr 得不到执行。
2. 更重要的,这个代码不符合 C++ 的惯用法。在 C++ 里,这种情况下有 99% 的可能性不应该使用堆内存分配,而应使用栈内存分配。这样写代码的,估计可能是从 Java 转过来的(偷笑)——但我真见过这样的代码
C++ 支持将对象存储在栈上面。但是,在很多情况下,对象不能,或不应该,存储在栈上。比如:
- 对象很大;
- 对象的大小在编译时不能确定;
- 对象生命周期超过了栈的生命周期
栈上的分配极为简单,移动一下栈指针而已。
栈上的释放也极为简单,函数执行结束时移动一下栈指针即可。
由于后进先出的执行过程,不可能出现内存碎片。
编译器会自动调用析构函数,包括在函数执行发生异常的情况。在发生异常时对析构函数的调用,还有一个专门的术语,叫栈展开(stack unwinding)。
RAII,完整的英文是 Resource Acquisition Is Initialization,是 C++ 所特有的资源管理方式。有少量其他语言,如 D、Ada 和 Rust 也采纳了 RAII。
一句话概况:构造中申请资源,析构中释放资源。
RAII 依托栈和析构函数,来对所有的资源——包括堆内存在内——进行管理。对 RAII 的使用,使得 C++ 不需要类似于 Java 那样的垃圾收集方法,也能有效地对内存进行管理。
RAII 的存在,也是垃圾收集虽然理论上可以在 C++ 使用,但从来没有真正流行过的主要原因。
智能指针本质上并不神秘,其实就是 RAII 资源管理功能的自然展现而已。
class shape_wrapper {
public:
explicit shape_wrapper(
shape* ptr = nullptr)
: ptr_(ptr) {}
~shape_wrapper()
{
delete ptr_;
}
shape* get() const { return ptr_; }
private:
shape* ptr_;
};这个类可以完成智能指针的最基本的功能:对超出作用域的对象进行释放。但它缺了点东西:
1. 只适用于 shape 类
2. 该类对象的行为不够像指针
3. 拷贝该类对象会引发程序行为异常
template <typename T>
class smart_ptr {
public:
explicit smart_ptr(T* ptr = nullptr)
: ptr_(ptr) {}
~smart_ptr()
{
delete ptr_;
}
T* get() const { return ptr_; }
private:
T* ptr_;
};目前这个 smart_ptr 的行为还是和指针有点差异的:
它不能用 * 运算符解引用
它不能用 -> 运算符指向对象成员
它不能像指针一样用在布尔表达式里
故增加下面几个方法
template <typename T>
class smart_ptr {
public:
…
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
operator bool() const { return ptr_; }
}class shared_count {
public:
shared_count() noexcept
: count_(1) {}
void add_count() noexcept
{
++count_;
}
long reduce_count() noexcept
{
return --count_;
}
long get_count() const noexcept
{
return count_;
}
private:
long count_;
};
template <typename T>
class smart_ptr {
public:
template <typename U>
friend class smart_ptr;
explicit smart_ptr(T* ptr = nullptr)
: ptr_(ptr)
{
if (ptr) {
shared_count_ =
new shared_count();
}
}
~smart_ptr()
{
printf("~smart_ptr(): %p\n", this);
if (ptr_ &&
!shared_count_
->reduce_count()) {
delete ptr_;
delete shared_count_;
}
}
template <typename U>
smart_ptr(const smart_ptr<U>& other) noexcept
{
ptr_ = other.ptr_;
if (ptr_) {
other.shared_count_->add_count();
shared_count_ = other.shared_count_;
}
}
template <typename U>
smart_ptr(smart_ptr<U>&& other) noexcept
{
ptr_ = other.ptr_;
if (ptr_) {
shared_count_ =
other.shared_count_;
other.ptr_ = nullptr;
}
}
template <typename U>
smart_ptr(const smart_ptr<U>& other,
T* ptr) noexcept
{
ptr_ = ptr;
if (ptr_) {
other.shared_count_
->add_count();
shared_count_ =
other.shared_count_;
}
}
smart_ptr&
operator=(smart_ptr rhs) noexcept
{
rhs.swap(*this);
return *this;
}
T* get() const noexcept
{
return ptr_;
}
long use_count() const noexcept
{
if (ptr_) {
return shared_count_
->get_count();
} else {
return 0;
}
}
void swap(smart_ptr& rhs) noexcept
{
using std::swap;
swap(ptr_, rhs.ptr_);
swap(shared_count_,
rhs.shared_count_);
}
T& operator*() const noexcept
{
return *ptr_;
}
T* operator->() const noexcept
{
return ptr_;
}
operator bool() const noexcept
{
return ptr_;
}
private:
T* ptr_;
shared_count* shared_count_;
};
template <typename T>
void swap(smart_ptr<T>& lhs,
smart_ptr<T>& rhs) noexcept
{
lhs.swap(rhs);
}
template <typename T, typename U>
smart_ptr<T> static_pointer_cast(
const smart_ptr<U>& other) noexcept
{
T* ptr = static_cast<T*>(other.get());
return smart_ptr<T>(other, ptr);
}
template <typename T, typename U>
smart_ptr<T> reinterpret_pointer_cast(
const smart_ptr<U>& other) noexcept
{
T* ptr = reinterpret_cast<T*>(other.get());
return smart_ptr<T>(other, ptr);
}
template <typename T, typename U>
smart_ptr<T> const_pointer_cast(
const smart_ptr<U>& other) noexcept
{
T* ptr = const_cast<T*>(other.get());
return smart_ptr<T>(other, ptr);
}
template <typename T, typename U>
smart_ptr<T> dynamic_pointer_cast(
const smart_ptr<U>& other) noexcept
{
T* ptr = dynamic_cast<T*>(other.get());
return smart_ptr<T>(other, ptr);
}实现移动的意义是减少运行的开销——在引用计数指针的场景下,这个开销并不大。



- 如果只从头、尾两个位置对 deque 进行增删操作的话,容器里的对象永远不需要移动。
- 容器里的元素只是部分连续的(因而没法提供 data 成员函数)。
- 由于元素的存储大部分仍然连续,它的遍历性能是比较高的。
- 由于每一段存储大小相等,deque 支持使用下标访问容器元素,大致相当于 index[i / chunk_size][i % chunk_size],也保持高效。
deque的扩容
map
│
├── chunk0 → [0][1][2][3] ← 已满
├── chunk1 → [4][5][6][7]
├── chunk2 → [8][9][10][11]
此时需要扩展 map
new_map
├── nullptr
├── nullptr
├── chunk_new
├── chunk0
├── chunk1
├── chunk2
├── nullptr
├── nullptr
std::array
std::array 本质上几乎就是:
template<typename T, size_t N>
struct array
{
T elems[N];
};例如:
std::array<int, 5> arr;std::array<int, 1000000> arr; 会发生什么?
1000000 * 4 约:4MB 可能直接:stack overflow
// 矩阵定义
typedef struct {
float* data;
size_t nrows;
size_t ncols;
} matrix;
enum matrix_err_code {
MATRIX_SUCCESS,
MATRIX_ERR_MEMORY_INSUFFICIENT,
…
};
// 分配矩阵
int matrix_alloc(matrix* ptr,
size_t nrows,
size_t ncols)
{
size_t size =
nrows * ncols * sizeof(float);
float* data = malloc(size);
if (data == NULL) {
return MATRIX_ERR_MEMORY_INSUFFICIENT;
}
ptr->data = data;
ptr->nrows = nrows;
ptr->ncols = ncols;
}
// 清理矩阵
void matrix_dealloc(matrix* ptr)
{
if (ptr->data == NULL) {
return;
}
free(ptr->data);
ptr->data = NULL;
ptr->nrows = 0;
ptr->ncols = 0;
}
// 矩阵乘法
int matrix_multiply(matrix* result,
const matrix* lhs,
const matrix* rhs)
{
int errcode;
if (lhs->ncols != rhs->nrows) {
return MATRIX_ERR_MISMATCHED_MATRIX_SIZE;
// 呃,得把这个错误码添到 enum matrix_err_code 里
}
errcode = matrix_alloc(
result, lhs->nrows, rhs->ncols);
if (errcode != MATRIX_SUCCESS) {
return errcode;
}
// 进行矩阵乘法运算
return MATRIX_SUCCESS;
}
//===============调用=================
matrix c;
// 不清零的话,错误处理和资源清理会更复杂
memset(c, 0, sizeof(matrix));
errcode = matrix_multiply(c, a, b);
if (errcode != MATRIX_SUCCESS) {
goto error_exit;
}
// 使用乘法的结果做其他处理
error_exit:
matrix_dealloc(&c);
return errcode;
class matrix {
…
private:
float* data_;
size_t nrows_;
size_t ncols_;
}
matrix::matrix(size_t nrows,
size_t ncols)
{
data_ = new float[nrows * ncols];
nrows_ = nrows;
ncols_ = ncols;
}
matrix::~matrix()
{
delete[] data_;
}
class matrix {
…
friend matrix
operator*(const matrix&,
const matrix&);
};
matrix operator*(const matrix& lhs,
const matrix& rhs)
{
if (lhs.ncols != rhs.nrows) {
throw std::runtime_error(
"matrix sizes mismatch");
}
matrix result(lhs.nrows, rhs.ncols);
// 进行矩阵乘法运算
return result;
}
// ===============调用====================
matrix c = a * b;栈展开
栈展开(Stack Unwinding)就是异常从抛出点向上寻找匹配 catch 的过程中,依次销毁沿途函数栈帧中的局部对象并执行其析构函数的过程。为了知道每个位置该析构哪些对象,编译器会生成额外的 异常展开表(unwind table),这也是开启 C++ 异常支持后可执行文件体积变大的主要原因。
因为每个可能抛异常的函数都要保存:
- 如何展开
- 析构哪些对象
- catch 在哪里
例如:
void f()
{
std::string s1;
std::string s2;
std::vector<int> v;
foo();
}编译器需要记录:
如果 foo() 抛异常:
v.~vector()
s2.~string()
s1.~string()上面这些信息被写进可执行文件。
因此:开启异常支持 通常会导致:10%~20% 甚至更多的二进制体积增长。
迭代器(Iterator)的本质是一个“像指针一样访问元素的对象”。 能前后移动,能读取和修改。
迭代器本质上是“容器访问逻辑的封装”。
高级理解(STL设计思想)
迭代器是“遍历容器”的统一抽象接口,把不同数据结构(数组、链表、红黑树、deque)的访问方式封装成一致的操作,使算法与容器解耦。
这正是 STL 的核心思想:
Algorithm + Iterator + Container
template<typename T>
class SimpleArray
{
public:
class iterator
{
public:
explicit iterator(T* ptr)
: ptr_(ptr)
{
}
T& operator*() const
{
return *ptr_;
}
iterator& operator++()
{
++ptr_;
return *this;
}
bool operator!=(const iterator& other) const
{
return ptr_ != other.ptr_;
}
private:
T* ptr_;
};
explicit SimpleArray(size_t n)
: size_(n)
{
data_ = new T[n];
}
~SimpleArray()
{
delete[] data_;
}
iterator begin()
{
return iterator(data_);
}
iterator end()
{
return iterator(data_ + size_);
}
T& operator[](size_t i)
{
return data_[i];
}
private:
T* data_;
size_t size_;
};
//===================使用====================
int main()
{
SimpleArray<int> arr(5);
for (int i = 0; i < 5; ++i)
{
arr[i] = i + 1;
}
for (auto it = arr.begin();
it != arr.end();
++it) // 核心
{
std::cout << *it << ' ';
}
}