读《深入理解C++11》
Tips
-
constexpr && const
constexpr 关键字的引入是为了解决 const 对 ROM 支持的不友好。const 变量只是不可修改,并不是编译时常量,无法应用于 ROM 等小型设备
-
lambda && functor
lambda 在内部被实现为仿函数
-
char
&&wchar_t
C++11 后 char 与
wchar_t
进行连接时编译器将自动转化为wchar_t
-
long long && 123LL
-
C++11 规定类的析构函数默认为
noexcept(true)
-
C++11 的就地初始化比初始化列表先执行
-
C++11:
sizeof(C::mem)
;C++98:sizeof((C*)(0)->mem)
-
C++11:
friend T
;C++98:friend class T
-
C++11:
typeid(type).hash_code()
-
C++11:常量表达式只能有一行 return
-
nullptr
&&nullptr_t
-
C++11 提供 unicode 支持
-
C++11 SFINAE
表达式中没有出现“ 外部于表达式本身” 的元素,比如说发生一些模板的实例化,或者隐式地产生一些 拷贝构造函数的话,这样的模板推导都不会产生 SFINAE 失败
-
extern template void fun<int>(int)
,少用 -
内联名字空间
inline namespace
-
构造函数的默认参数不会被继承。继承来的构造函数是隐式声明的
-
构造函数不能同时使用委派和初始化列表;委派构造函数可以捕获异常
-
auto && cv-qualifer
声明为引用类型 auto 会保持原有变量的属性
-
三元运算符的性能缺陷:
#define Max(a,b) ((a)>(b)):(a):(b)
-
纯右值 && 将亡值
将亡值是被强制转化为右值的返回值
-
万能的
cont T&
C++ 98 开始就允许 const T& 绑定到右值,并可以延长右值的生命周期与引用值相同。这种优化减少了拷贝与析构的损耗。常量左值引用是“万能”的,如果重载没有实现右值引用的接口,则编译器自动调用常量左值引用接口
-
构造时为成员变量手动调用 move
编写右值接口时要显式的调用 move 函数将成员变量转化为右值以触发成员变量的移动语意
-
非受限联合体 / 类内匿名联合体 / placement new
任何非引用类型都可以成为联合体的数据成员,这样的联合体即所谓的非受限联合体( Unrestricted Union)
非受限联合体默认会删除非 POD 变量的构造函数,所以需要手动调用 placement new 和对应的析构
-
Big5 的自动生成机制
以在 C++11 中,拷贝构造/赋值和移动构造/赋值函数必须同时提供,或者同时不提供,程序员才能保证类同时具有拷贝和移动语义。只声明其中一种的话,类都仅能实现一种语义
新概念
forward
所谓完美转发(perfectforwarding),是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数
完美转发的引入是为了解决右值属性传递的一些问题,按照右值的一般理解:没有名字不能取地址的值被称为右值,但右值参数在函数内是左值。比如函数 f(C &&c)
,c
在函数f
内部是当作左值看待的。如果在函数 f 内调用了另一个函数 g(C &&c)/g(const C &c)
,那么编译器默认会选择 g(const C &c)
,示例如下:
#include <utility>
#include <iostream>
class A{ };
void tt(A &&a) { std::cout << "const A &&a" << std::endl; }
void tt(const A &a) { std::cout << "const A &a" << std::endl; }
void bb(A &&a) { tt(a); }
// void cc(A &&a) { tt(std::forward(a)); } // invalid usage of forward
template<typename T>
void dd(T &&a) { tt(std::forward<T>(a)); } // 只有左值引用 forward 不需要模板参数,如下文定义
int main() {
tt(A()); // const A &&a
bb(A()); // const A &a
dd(A()); // const A &&a
A a;
tt(a); // const A &a
// bb(a); // invalid call
dd(a); // const A &a
}
C++11 中 forward 的实现方式如下(注意第二种实现方法必须提供模板参数):
template <class T>
inline T&& forward(typename std::remove_reference<T>::type& t) noexcept {
return static_cast<T&&>(t);
}
template <class T>
inline T&& forward(typename std::remove_reference<T>::type&& t) noexcept {
static_assert(
!std::is_lvalue_reference<T>::value, "Can not forward an rvalue as an lvalue.");
return static_cast<T&&>(t);
}
为实现上述功能,C++ 11 在模板中引入了引用折叠的概念:
TR R
T& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T& && -> T& // rvalue reference to cv TR -> TR (lvalue reference to T)
T&& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T&& && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)
initializer_list
#include <iostream>
#include <vector>
using namespace std;
class Mydata
{
public:
Mydata &operator[](initializer_list<int> l) {
for (auto i = l.begin(); i != l.end(); ++i) idx.push_back(*i);
return *this;
}
Mydata &operator=(int v) {
if (idx.empty() != true) {
for (auto i = idx.begin(); i != idx.end(); ++i) {
d.resize((*i > d.size()) ? *i : d.size());
d[*i - 1] = v;
}
idx.clear();
}
return *this;
}
void Print() {
for (auto i = d.begin(); i != d.end(); ++i)
cout << *i << " ";
cout << endl;
}
private:
vector<int> idx; // 辅助数组, 用于记录 index
vector<int> d;
};
int main() {
Mydata d;
d[{2, 3, 5}] = 7;
d[{1, 4, 5, 8}] = 4;
d.Print(); // 4 7 7 4 4 0 0 4
}
自定义字面值
#include <iostream>
struct Watt{ unsigned int v; };
Watt operator "" _w( unsigned long long v) {
return {(unsigned int) v};
}
int main() {
Watt capacity = 1024_w;
}
decltype
编译时行为
decltype 的使用比较繁琐,非库代码一般较少使用
usingsize_t = decltype(sizeof(0));
usingptrdiff_t = decltype((int*)0(int*)0);
usingnullptr_t = decltype(nullptr);
std::result_of
template <class> struct result_of;
template <class F, class... ArgTypes>
struct result_of<F(ArgTypes...)> {
typedef decltype(std::declval<F>()(std::declval<ArgTypes>()...)) type;
};
返回值推导
template<typename T, typename U>
auto sum(T t, U u) -> decltype(t+u) { return t+u; }
变长模板
template <typename... Elements> class tuple; // 变长模板的声明
template< typename Head, typename... Tail> // 递归的偏特化定义
class tuple<Head, Tail...> : private tuple< Tail...> { Head head; };
template<> class tuple<> {}; // 边界条件
原子变量与内存序
default && delete && explicit
default 和 POD 类型息息相关
default 和默认 Big5 的自动生成规则息息相关
不要同时使用 explicit && default 或者 explicit && delete
class ConvType {
public: ConvType( int i) {};
ConvType( char c) = delete; // 删除 char 版本
};
alignas/alignof
因为硬件等原因 ,比如cache line (64B),总线宽度(64bit) 等,数据是否对齐会影响系统的性能,某些指令场景下未对齐的数据会造成系统的崩溃
以 64位 CPU 为例,cpu 以 64bit 为单位(8字节)从内存中读取数据,如果 int 类型的变量对齐到内存 1 字节地址边界,那么 cpu 在读取这个变量时很可能需要读两次,每次读 int 的一部分;如果 int 对齐到 4 字节边界,那么 CPU 一次肯定可以获取整个 int 变量(可以用反正法证明)
下面代码片段展示了 alignas/alignof 的使用方法。x64 cpu 支持矢量指令,一次可以操作多个变量,默认情况下以 8 字节为边界进行对齐,这不利于 CPU 取值(32 字节是扩展对齐方式,标准对这类行为没有定义)
#include <iostream>
using namespace std;
struct ColorVector {
double r, g, b, a;
};
// 非标准对齐,使用时要注意
struct alignas(32) AlgColorVector {
double r, g, b, a;
};
int main() {
// alignof(ColorVector): 8
// alignof(AlgColorVector): 32
// alignof(std::max_align_t): 16
cout << "alignof(ColorVector): " << alignof(ColorVector) << endl;
cout << "alignof(AlgColorVector): " << alignof(AlgColorVector) << endl;
cout << "alignof(std::max_align_t): " << alignof(max_align_t) << endl;
return 0;
}
std::max_align_t
系统支持的对齐数值是有限的,可以通过 alignof(std::max_align_t)
来判断对齐上限,通常是最大标量(long double)长度
使用超过默认对齐字节的对齐长度,标准不保证所有编译器正常
align/aligned_storage/aligned_union
// aligned_storage example
#include <iostream>
#include <type_traits>
struct A { // non-POD type
int avg;
A (int a, int b) : avg((a+b)/2) {}
};
typedef std::aligned_storage<sizeof(A),alignof(A)>::type A_pod;
int main() {
A_pod a,b;
new (&a) A (10,20);
b=a;
std::cout << reinterpret_cast<A&>(b).avg << std::endl;
return 0;
}
通用属性
如下程序片段,程序中 const 属性告诉编译器,该函数返回值只依赖于输入,不会改变函数外的数据,因此编译器可以对 area(3) 进行优化处理,只对函数调用一次,后续将 area(3) 视为常量进行操作,将大大提升程序性能
extern int area(int n) __attribute__ ((const))
int main()
{
int areas=0;
for(int i=0;i<10;++i)
{
areas+=area(3)+i;
}
}
自C++11开始,C++ 拥有统一形式的通用属性申明方式,语法格式如下:
[[ carries_dependency ]] func();
C++ 11/14/17 以后常用属性有:
[[noreturn]]、[[carries_dependency]]、[[deprecated]]、[[deprecated(“reason”)]]
POD
参考1:https://mariusbancila.ro/blog/2020/08/10/no-more-plain-old-data/
参考2:https://docs.microsoft.com/en-us/cpp/cpp/trivial-standard-layout-and-pod-types?view=msvc-160
参考3:https://en.cppreference.com/w/cpp/language/initialization
A POD type is a type that is both trivial and standard-layout. This definition must hold recursively for all its non-static data members.
POD 在 C++ 20 以后被 trivial 和 standard layout 这两个概念替换。简单一些可以认为 POD 类型是 C++ 中与 C 兼容的内存布局,没有 C 不理解的内存成分且拷贝没副作用(比如引用增加等)。以C++中的继承为例,因为 C 中没有继承等概念,所以 C++ 中有继承与多态而与 C 不兼容的变量都不是 POD,某些 OB 对象是 POD 但 OO 对象一定不是POD。可以通过 std::is_pod
判断是否 POD: std::is_pod
= std::is_trivaial
+ std::is_standard_layout
,is_pod
慢慢被弃用,应该使用后两个方法
-
trival ,
std::is_trivial
&&=default
没有自定义的 BIG5 和析构函数,且没有虚函数和虚基类
可以从一个例子来说明 trivial 的意义:对象的内存可以通过 malloc 创建然后通过 free 删除
default 关键字的存在可以将自定义了构造函数的类恢复为 POD。一般定义了自己的构造函数后默认的构造函数就不再生成,但使用 default 可以强制编译器生成默认构造等方法
-
standard layout,
std::is_standard_layout
,标准布局的对象内存是可预测的。不同编译器对 C++ 继承对象的实现方式不同,所以包含继承关系的对象一般不是标准布局,因为其内存布局强依赖编译器
POD 的优势
- 字节赋值,比如直接使用底层的 memory copy 进行拷贝
- 提供与 C 兼容的内存布局
- 保证静态初始化的安全有效