本文摘自一些 C++ 学习资料的要点笔记。

《罗剑锋的 C++ 实战笔记》

构建工具

C++ 常用构建工具:cmake、scons。目前阶段,C++11 是必须要掌握的。

C++ 语言的编程范式

什么是编程范式呢?关于这个概念,没有特别权威的定义,一个比较通俗的解释:“编程范式”是一种”方法论”,就是指导你编写代码的一些思路、规则、习惯、定式和常用语。C++ 是一种多范式的编程语言。具体来说,现代 C++(11/14 以后)支持”面向过程""面向对象""泛型""模板元""函数式”这五种主要的编程范式。具体如下:

  • 面向过程是 C++ 里最基本的一种编程范式。它的核心思想是”命令”,通常就是顺序执行的语句、子程序(函数),把任务分解成若干个步骤去执行,最终达成目标。
  • 面向对象是 C++ 里另一个基本的编程范式。它的核心思想是”抽象”和”封装”,倡导的是把任务分解成一些高内聚低耦合的对象,这些对象互相通信协作来完成任务。它强调对象之间的关系和接口,而不是完成任务的具体步骤。
  • 泛型编程是自 STL(标准模板库)纳入到 C++ 标准以后才逐渐流行起来的新范式,核心思想是”一切皆为类型”,或者说是”参数化类型""类型擦除”,使用模板而不是继承的方式来复用代码,所以运行效率更高,代码也更简洁。
  • 模板元编程,它的核心思想是”类型运算”,操作的数据是编译时可见的”类型”,所以也比较特殊,代码只能由编译器执行,而不能被运行时的 CPU 执行。
  • 函数式,所谓的”函数式”并不是 C++ 里写成函数的子程序,而是数学意义上、无副作用的函数,核心思想是”一切皆可调用”,通过一系列连续或者嵌套的函数调用实现对数据的处理。

如果是开发直接面对用户的普通应用,那么你可以再研究一下”泛型”和”函数式”,就基本可以解决 90% 的开发问题了;如果是开发面向程序员的库,那么你就有必要深入了解”泛型”和”模板元”,优化库的接口和运行效率。

《现代 C++ 实战 30 讲》

移动语义、引用坍塌

根据 C++ 的规则,如果我提供了移动构造函数而没有手动提供拷贝构造函数,那后者自动被禁用。

临时对象(prvalue)生命周期问题,C++ 的规则是:一个临时对象会在包含这个临时对象的完整表达式估值完成后、按生成顺序的逆序被销毁,除非有生命周期延长发生。为了方便对临时对象的使用,C++ 对临时对象有特殊的生命周期延长规则。这条规则是:如果一个临时对象(prvalue)被绑定到一个引用上,它的生命周期则会延长到跟这个引用变量一样长。

我们已经讲了对于一个实际的类型 T,它的左值引用是 T&,右值引用是 T&&。那么:

  1. 是不是看到 T&,就一定是个左值引用?
  2. 是不是看到 T&&,就一定是个右值引用?

对于前者的回答是”是”,对于后者的回答为”否”。

关键在于,在有模板的代码里,对于类型参数的推导结果可能是引用。我们可以略过一些繁复的语法规则,要点是:

  • 对于 template <typename T> foo(T&&) 这样的代码,如果传递过去的参数是左值,T 的推导结果是左值引用;如果传递过去的参数是右值,T 的推导结果是参数的类型本身。
  • 如果 T 是左值引用,那 T&& 的结果仍然是左值引用——即 type& && 坍缩成了 type&
  • 如果 T 是一个实际类型,那 T&& 的结果自然就是一个右值引用。

完美转发

事实上,很多标准库里的函数,连目标的参数类型都不知道,但我们仍然需要能够保持参数的值类别:左值的仍然是左值,右值的仍然是右值。这个功能在 C++ 标准库中已经提供了,叫 std::forward。它和 std::move 一样都是利用引用坍缩机制来实现。

#include<iostream>
using namespace std;

#define FORWARD

class shape {
public:
    shape(){}
    virtual ~shape(){}
};

class circle: public shape {
};


void foo(const shape&) {
    puts("foo(const shape&)");
}

void foo(shape&&) {
    puts("foo(shape&&)");
}

# ifndef FORWARD
void bar(const shape& s) {
    puts("bar(const shape&)");
    foo(s);
}

void bar(shape&& s) {
    puts("bar(shape&&)");  
    foo(s);
}
#else 
template<class T>
void bar(T&& s) {
    foo(std::forward<T>(s));    // 完美转发,原有是右值,作为参数,依然能保证在 foo 中是右值,调用 foo(shape&&)
}
#endif

int main() {
    bar(circle());

    return 0;
}