本文摘自一些 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&&。那么:
- 是不是看到
T&,就一定是个左值引用? - 是不是看到
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;
}