C++11新特性

类型推导 auto & decltype

  • auto用于推导变量类型
  • decltype用于推导表达式类型

右值引用

  1. 概念
    • 左值:可以取地址并且有名字,可以放在等号左边
    • 右值:不可以取地址且没有名字,不可以放在等号左边
      1
      2
      3
      int a = b + c; 
      int a = 4;
      // a是左值, b+c的返回值是右值, 4也是右值
    • 左值引用:对左值进行引用
    • 右值引用:对右值进行引用
  2. std::move
    将左值强制转换为右值
  3. std::forward
    类型转换,可以转成左值或者右值
  4. 用途
    • 实现移动语义,避免深拷贝,提升程序的性能。

列表初始化

Example

构造函数初始化列表和构造函数内部赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CExample {
public:
int a;
float b;
//构造函数初始化列表
CExample(): a(0),b(8.8)
{}
//构造函数内部赋值
CExample()
{
a=0;
b=8.8;
}
};

必须使用初始化列表的情况?

成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Person {
public:
Person(const std::string& name, int age) : name(name), age(age) {}

private:
std::string name;
int age;
};

// 错误:尝试在构造函数体内初始化person成员变量
// 在进入构造函数体之前,所有的成员变量都已经被初始化了
// Person 类没有默认构造函数,所以在 Employee 的构造函数体中试图使用 person(name, age); 来初始化 person 成员变量时,会报错,因为 person 成员变量在进入构造函数体之前已经被默认初始化了,而 Person 类没有默认构造函数可供调用。
class Employee {
public:
Employee(const std::string& name, int age) {
person(name, age);
}

private:
Person person;
};

// 正确:使用成员初始化列表初始化person成员变量
class Employee {
public:
Employee(const std::string& name, int age) : person(name, age) {}

private:
Person person;
};

智能指针

C++智能指针主要是为了负责自动释放所指向的对象。

Example: 手写Shared_ptr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <bits/stdc++.h>

template<typename T>
class myShared_ptr{
public:
explicit myShared_ptr() {
_ptr = NULL;
_useCnt = new size_t(0);
}
explicit myShared_ptr(T *p) {
_ptr = p;
_useCnt = new size_t(1);
}
myShared_ptr(const myShared_ptr& p) {
_ptr = p._ptr;
_useCnt = p._useCnt;
++(*_useCnt);
}
~myShared_ptr() {
--(*_useCnt);
if(*_useCnt <= 0) {
delete _ptr;
delete _useCnt;
}
}
myShared_ptr& operator= (const myShared_ptr& p) {
if(_ptr == p._ptr) {
return *this;
}
if(_ptr) {
--(*_useCnt);
if((*_useCnt) <= 0) {
delete _ptr;
delete _useCnt;
}
}
_ptr = p._ptr;
_useCnt = p._useCnt;
++(*_useCnt);
return *this;
}
void swap(myShared_ptr& p) {
std::swap(*this, p);
}
void getCnt() {
std::cout << *_useCnt << std::endl;
}
private:
T* _ptr;
size_t* _useCnt;
};

int main() {
myShared_ptr<int> p1;
p1.getCnt();

myShared_ptr<int> p2(new int(1));
p2.getCnt();

myShared_ptr<int> p3(p2);
p2.getCnt();
p3.getCnt();

p1 = p3;
p1.getCnt();
p3.getCnt();

myShared_ptr<int> p4(new int(1));
p4.swap(p3);
p3.getCnt();
p4.getCnt();
return 0;
}

三种智能指针

  • std::shared_ptr:它是一种共享拥有(shared ownership)的智能指针。多个 std::shared_ptr 对象可以共同拥有同一个资源,并且会自动地跟踪资源的引用计数。
  • std::unique_ptr:它是一种独占拥有(exclusive ownership)的智能指针。一个 std::unique_ptr 对象拥有资源的独占权,并且不能与其他 std::unique_ptr 或 std::shared_ptr 共享同一个资源。当 std::unique_ptr 对象超出作用域或被显式地释放时,它所拥有的资源会被释放。
  • std::weak_ptr:用于解决 std::shared_ptr 的循环引用问题。std::weak_ptr 允许你观测一个资源,而不会增加该资源的引用计数。std::weak_ptr 常用于解决 std::shared_ptr 的循环引用问题。当两个或多个对象相互持有 std::shared_ptr,形成循环引用时,资源可能无法被释放。通过将其中一个或多个 std::shared_ptr 改为 std::weak_ptr,可以打破循环引用,允许资源在不再被引用时被释放。

shared_ptr有哪些安全隐患?

  1. 引用计数的加减操作是否线程安全?
    • 对于引用计数这一变量的存储,是在堆上的,多个shared_ptr的对象都指向同一个堆地址,是线程安全的。
  2. shared_ptr修改指向时,是否线程安全。
    • 多线程代码操作的是同一个shared_ptr的对象时时不安全的。当在多线程中操作同一shared_ptr对象时,数据指针要改指向,sp原先指向的引用计数的值要减去1,other_sp指向的引用计数值要加一。这几步操作加起来并不是一个原子操作。
    • 多线程代码操作的不是同一个shared_ptr的对象时,此时发生多线程中修改sp指向的操作时,不会出现非预期的异常行为。但是不代表修改其管理的数据时,不会出现线程安全问题。

C++14新特性

返回值类型推导

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

int main() {
cout << func(4) << endl;
return 0;
}

lambda表达式参数类型推导

1
2
auto f = [] (int a) { return a; }; // C++11
auto f = [] (auto a) { return a; }; // C++14

C++17新特性

if-switch语句初始化

1
2
3
if (int a = GetValue(); a < 101) {
cout << a;
}

内联变量

解决C++类的静态成员变量在头文件中是不能初始化

1
2
3
struct A {
inline static const int value = 10;
}

namespace嵌套写法优化

1
2
3
namespace A::B::C {
void func();
}