C++这门语言真的是博大精深,在阅读PyTorch源码的时候感触尤为深刻,在此记录一些我认为很好的编程实践以及一些C++的“新”特性。
条件检查
在程序需要判断一些条件才能执行某些操作的时候,我们通常会写如下代码:
1 | if (cond == true) { |
这些if
语句可以简洁为一行代码,如使用assert
语句,但如果你不希望程序就此终止的话,也可以自定义相应的处理函数或宏,如果需要检查的条件很多的话,这样进行替换就会使代码整洁许多。下面是在pytorch/c10
中的条件判断处理宏(仅供参考):
1 |
|
特性测试
宏定义的运用在C/C++中至关重要,宏可以用来解决一些兼容性的问题,并且使代码保持统一。如constexpr
关键字在不同版本的C++中范围不同,那么已知一个函数可以在C++14及以上的版本中声明constexpr
但在之前的版本中不行,该怎么办呢?一个既能兼容不同编译器又能保持代码整洁统一的方法就是使用宏定义,如:
1 | AT_CPP14_CONSTEXPR const T& front() const { |
AT_CPP14_CONSTEXPR
的定义为:
1 |
通过特性测试检查特性的指定版本来判断能否使用该关键字,如果版本大于我们的要求,则把宏定义为该关键字,否则定义为空。
使用 nullptr 而不是 NULL
在C++中,宏NULL
只是字面量0,而nullptr
才是字面量空指针。
函数不要太长
在PyTorch中,函数都很短,一般不超过30行,尽量把函数变得精简。
&&
当 &&
出现在参数列表中或函数返回值处时表示右值引用(RValue-Reference)。所谓右值引用就是对右值的引用,而右值就是只能出现在等号右边的值,也可以理解为没有内存地址的值。如数字字面量,a+1
(int a;
) 等都是右值。下面是一个&&
用法的例子:
1 | void foo(int&& a) |
=default 和 = delete
详见:https://www.ibm.com/developerworks/cn/aix/library/1212_lufang_c11new/index.html
简单来说,构造函数、析构函数等特殊函数可以用 =default
来让编译器为其提供默认构造函数或默认析构函数。而 =delete
可以禁用某个函数,如:
1 | class X{ |
智能指针
Unique_Ptr
同时只能有一个智能指针对象指向某块内存。
特性:
无法进行复制构造与赋值操作
1
2
3unique_ptr<int> ap(new int(88 );
unique_ptr<int> one (ap) ; // error
unique_ptr<int> two = one; // error可以进行移动构造和移动赋值
1
2
3
4
5
6
7
8
9unique_ptr<int> GetVal() {
unique_ptr<int> up(new int(88));
return up;
}
unique_ptr<int> uPtr = GetVal(); //ok
unique_ptr<int> up(new int(88));
unique_ptr<int> uPtr2 = std:move(up); // ok
shared_ptr
是自带引用计数的智能指针,每一个`shared_ptr
的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,删除所指向的堆内存。shared_ptr
内部的引用计数是安全的,但是对象的读取需要加锁,阅读更多。
Weak_Ptr
weak_ptr
用来表达临时所有权的概念:当某个对象只有存在时才需要被访问,而且随时可能被他人删除时,可以使用 weak_ptr
来跟踪该对象。需要获得临时所有权时,则将其转换为 shared_ptr
,此时如果原来的 shared_ptr
被销毁,则该对象的生命期将被延长至这个临时的 shared_ptr
同样被销毁为止。
weak_ptr
是为了配合shared_ptr
而引入的一种智能指针,它指向一个由shared_ptr
管理的对象而不影响所指对象的生命周期,也就是将一个weak_ptr
绑定到一个shared_ptr
不会改变shared_ptr
的引用计数。不论是否有weak_ptr
指向,一旦最后一个指向对象的shared_ptr
被销毁,对象就会被释放。从这个角度看,weak_ptr
更像是shared_ptr
的一个助手而不是智能指针。C++中提供了lock
函数来实现访问功能。如果对象存在,lock()
函数返回一个指向共享对象的shared_ptr
,否则返回一个空shared_ptr
:
1 | class A |
阅读更多。
变参模板
变参模板声明:
1 | template <class... T> |
省略号的作用有两个:
- 声明一个参数包
T… args
,这个参数包中可以包含0到任意个模板参数; - 在模板定义的右边,可以将参数包展开成一个一个独立的参数。
基本用法
1 | template <class... T> |
递归展开
1 | template<typename T> |
初始化列表+逗号表达式展开
1 | template <class T> |
这个例子将分别打印出1,2,3,4四个数字。这种展开参数包的方式,不需要通过递归终止函数,是直接在expand
函数体中展开的, printarg
不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。
expand
函数中的逗号表达式:(printarg(args), 0)
,先执行printarg(args)
,再得到逗号表达式的结果0
。同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)…}
将会展开成((printarg(arg1),0), (printarg(arg2),0), (printarg(arg3),0), ...)
,最终会创建一个元素值都为0
的数组int arr[sizeof...(Args)]
。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)
打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。
完美转发
1 | template<class F, class... Args> |
std::forward
可以保持参数的左值或右值性质,所以叫完美转发。
函数返回值类型后置
例子:
1 | auto ReadyQueue::push(FunctionTask item) -> void { |
解析:https://blog.csdn.net/fjb2080/article/details/7527349