C++11新特性

c++11相比c++17就比较熟悉了,原因当然是实践的比较多,自然就熟悉了。本文还是介绍我比较熟悉和比较简单的特性,太过熟悉的,比如nullptr,我不会讲也没有讲的必要,全部特性参照C++11 - cppreference.com

Core language features

auto & decltype

  • auto:让编译器在编译期就推导出变量的类型。

    很好用的关键字,但是不能滥用,否则会影响代码可读性,比如int,double这种简单类型就不要使用,一些不关心其具体类型的变量(比如lambda表达式)或者复杂类型就使用auto。

  • decltype:编译器推导表达式的类型。

具体的推导规则看了就忘实在记不住。

default & delete & final & override & explicit

  • default : 使用默认的函数定义,只用用于特殊成员函数(构造,析构之类)。
1
2
3
4
5
6
7
8
9
struct A {
A() = default;
int a;
A(int i) { a = i; }
};
int main() {
A a;
return 0;
}
  • delete :如果没有定义特殊成员函数,编译器期就会生成默认的特殊成员函数,delete可以阻止这种行为。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//禁止对象的拷贝与赋值
struct A {
A() = default;
A(const A&) = delete;
A& operator=(const A&) = delete;
int a;
A(int i) { a = i; }
};

int main() {
A a1;
A a2 = a1; // 错误,拷贝构造函数被禁用
A a3;
a3 = a1; // 错误,拷贝赋值操作符被禁用
}
  • final :用于指定虚函数不能被重写或者类不能被继承。
1
2
3
4
5
6
7
8
class ChildOfBase : public Base {
private:
int dontChangeMe() const final { return 1; }
};

class BadChildOfChild : public ChildOfBase {
int dontChangeMe() const override; //ERROR
}
1
2
3
4
5
6
7
class DontDeriveFromMe final {
// ...
};

class Failure : public DontDeriveFromMe { //ERROR
// ...
};
  • override:用于确保一个函数是虚函数且重写了基类的虚函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Base {
virtual void func() {
cout << "base" << endl;
}
};

struct Derived : public Base{
void func() override { // 确保func被重写
cout << "derived" << endl;
}

void fu() override { // error,基类没有fu(),不可以被重写

}
};
  • explicit:专门用来修饰构造函数,表示只能显示构造,不能被隐式转换。
1
2
3
4
5
6
7
8
9
10
11
struct A {
explicit A(int value) {
cout << "value" << endl;
}
};

int main() {
A a = 1; // error,不可以隐式转换
A aa(2); // ok
return 0;
}

委托构造函数 & 继承构造函数

  • 委托构造函数:允许同一个类中的一个构造函数(委托构造函数)调用另一个构造函数(目标构造函数),即在委托构造函数的初始化列表调用目标构造函数,执行顺序是先目标构造函数再委托构造函数。委托构造函数可以减少代码重复,避免相同的构造逻辑写多次。
1
2
3
4
5
6
7
8
9
10
11
12
struct A {
A(){}
A(int a) { a_ = a; }

A(int a, int b) : A(a) { b_ = b; }

A(int a, int b, int c) : A(a, b) { c_ = c; }

int a_;
int b_;
int c_;
};
  • 继承构造函数:允许派生类可以使用基类的构造函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Base {
Base() {}
Base(int a) { a_ = a; }

Base(int a, int b) : Base(a) { b_ = b; }

Base(int a, int b, int c) : Base(a, b) { c_ = c; }

int a_;
int b_;
int c_;
};

struct Derived : Base {
using Base::Base;//继承构造函数
};

int main() {
Derived a(1, 2, 3);
return 0;
}

静态断言(static_assert)

c++11引入static_assert声明,用于在编译期间检查,如果第一个参数值为false,则打印message,编译失败。

1
static_assert(true/false, message);

enum class

新增带作用域的枚举类型,普通的枚举可以自动转换为整型,且不同的枚举可以相互比较,以至于产生潜藏的bug,而带作用域的枚举就不会有这些问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
enum class AColor {
kRed,
kGreen,
kBlue
};
enum class BColor {
kWhite,
kBlack,
kYellow
};


int main() {
if (AColor::kRed == BColor::kWhite) { // 编译失败
cout << "red == white" << endl;
}
return 0;
}

所以一定要使用带作用域的枚举。

thread_local

顾名思义,线程本地,被thread_local修饰的变量具有线程周期,每一个线程都拥有其只拥有一个该变量的独立实例。

attribute

  • [[noreturn]]:表明一个函数一定不会返回,注意是不会返回,而不是没有返回值

  • [[carries_dependency]]:看不懂

    允许我们将dependency跨越函数进行传递,用于避免在弱一致性模型平台上产生不必要的内存栅栏导致代码效率降低。

列表初始化(list initialization)

在变量名后加上初始化列表来进行对象的初始化

1
2
int a{123};
std::string s{"test"}

std::initializer_list是实现列表初始化的关键,它可以接受任意长度的初始化列表,可以理解为存储特定类型的数组

1
2
3
4
5
6
7
8
9
10
11
void f(std::initializer_list<double> il);

void g(float x)
{
f({1, x, 3});
}

void h()
{
f({1, 2, 3});
}

constexpr

constexpr修饰的是真正的常量,它会在编译期间就会被计算出来,整个运行过程中都不可以被改变,constexpr可以用于修饰函数,这个函数的返回值会尽可能在编译期间被计算出来当作一个常量,但是如果编译期间此函数不能被计算出来,那它就会当作一个普通函数被处理

1
2
3
4
5
6
7
8
9
constexpr int func(int i) {
return i + 1;
}

int main() {
int i = 2;
func(i);// 普通函数
func(2);// 编译期间就会被计算出来
}

lambda表达式

其中func是可以当作lambda表达式的名字,作为一个函数使用,capture是捕获列表,params是参数表,opt是函数选项(mutable之类), ret是返回值类型,func_body是函数体。

1
auto func = [capture] (params) opt -> ret { func_body; };

lambda表达式允许捕获一定范围内的变量,但是不推荐默认捕获,明确捕获的变量

  • []不捕获任何变量

  • [&]引用捕获,捕获外部作用域所有变量,在函数体内当作引用使用

  • [=]值捕获,捕获外部作用域所有变量,在函数内内有个副本使用

  • [=, &a]值捕获外部作用域所有变量,按引用捕获a变量

  • [a]只值捕获a变量,不捕获其它变量

  • [this]捕获当前类中的this指针

Other

其他的如智能指针,左右值之类比较重要的,需要单独写一篇文章来介绍。