录入C++11关键字的说明以及相关重点知识

主流的编译器gcc,用c语言写的,后期由Google,从6.0把gcc用c++重新写了一套

苹果公司开发的编译器主流的是llvm,前端是clang

关键字

alignas(c++11起)

字节对齐方式,alignas都是2的倍数,除了0之外,默认都是0,1,2,4,8,16,32,64…….这跟c++寻找方式有关

alignof(C++11起)

实际占有字节大小,obj中虽然是1个字节的char+4个字节的int,但是内存中为了查找方便采用了对齐,直接新增3个字节在char后面,所以sizeof为8,但是alignof能够显示最原始的大小

and

and等价于==

and_eq

a and_eq b 等价于 a = a & b

asm

内嵌汇编语言

auto

自动类型,让编译器去自动选择类型

bitand,bitor

按位于,按位或

bool

布尔值,只有一个字节,但是强转char变成范围0~255

以下结果为True,有的编译器也会跑出what的结果

所以对布尔值的写法应该写成if(a)不要if(a == true)

break

用来跳出循环,switch,while,for三个地方

case

配合switch使用

catch

捕捉异常

char

char按照语言规范至少能够表示255个值就行了,用1个字节8个位就行了

在linux平台下char和unsigned char 等价

windows平台下则char和signed char等价

char16_t,char_32_t

像中文韩文日文,256个字符可能不够用,所以有2个字节,4个字节的char长度

很多平台都用Unicode,像中文就用UTF-8

compl等价符号~

照顾某些国家键盘没有这个字符

concept

概念TS,当出错的时候,错误信息冗长不清晰,对模板编程很模糊,能够有错误码

const

不变性引入,区别于其他语言的关键,如果能用const尽量用const

与multithread多线程编程密不可分

const最早c++引入,c语言看c++引入的东西不错也引入const概念

constexpr

从代码到程序,编译+链接

拓展c++在编译器的功能,当在编译器产生足够多的信息,把值就产生出来

如果在编译器就能确定的话,直接把值求出来

const_cast

将const语义转非const语义的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const int j = 3;
// &j 默认类型为 const int *
// 现在通过const_cast强转成了int *,让const的不变性变成了可变性

//例如
void testA(const int & a){
//默认情况下a不变
}

void testB(const int & b){
//默认情况下b不变
}

void testConstCase(){
//默认情况下a不变
const int j = 3;
std::thread aa([&j]{testA(j);});
std::thread bb([&j]{testA(j);});
int *pj = const_cast<int*>(&j);
*pj = 4; //这个时候const不变性承诺进行破坏
}

continue

继续循环,与break对应

decltype(c++11起)

与auto差不多类型,等价于

1
2
3
4
5
6
7
auto aa = a->x;	
//等价
decltype(a->) y; //double

decltype((a->x)) z = y; //double & 类型值的引用
//等价于
auto& cc = y;

与auto不同得用途范围,推到表达式类型

default

switch case中默认选项

delete

delete等价于执行析构+free

dynamic_cast

将父类制作转换子类指针

大致常用三种cast,dynamic_cast,const_cast,static_cast

1
2
3
Base bb;
dynamic_cast<Derived&>bb
//取引用会抛出相关异常,取不到具体的引用

enum

c++为防止作用于复用不到,加了enum class

传统老Enum的sizeof不明确,根据enum里面的value来决定它的sizeof可能是char 可能是unsigned char

1
2
//所以可以限定char大小,默认不写会等价Int类型
enum class NewColor: char{ Red, Blue};

用enum替代bool传参

explicit

明确告诉编译器要做什么类型,阻止隐式转换,阻止类似Java自动拆装箱

1
2
3
4
5
6
7
8
9
10
A a2(2);//允许
B b2(2); //允许
int na1 = a1; //ok
int nb1 = b2; //不允许
int nb1 = b2; //不允许
int na1 = a1; //ok
int na2 = static_cast<int>(a1); // 允许
int nb2 = static_cast<int>(b2); //允许
A a4 = (A)1;//允许
B b4 = (B)1; //允许

这样写的好处,在函数传参的时候可以默认减少很多Bug

export

之前运用模板都是在头文件,后期能有这个在cpp文件里实现

因为没有编译器支持,后期就c++11废弃不用

extern

对于C语言和C++没有什么变化

friend

使用的场景比较少,比较特殊

友元函数是可以直接访问类的私有成员的非成员函数。它是定义在类外的普通函数,它不属于任何类,但需要在类的定义中加以声明,声明时只需在友元的名称前加上关键字friend。

goto

为了兼容性的高级语言,goto很容易出错,但是像break只能跳出一层,goto可以随意跳

inline

普通执行函数给对应函数开堆栈空间,如果函数非常小,开堆栈反而时间比函数调用多,所以拿空间换时间,inline会让空间变大,但是不需要开堆栈空间

加inline会把代码直接展开函数内部的表达式

inline函数会不会坏也不一定,现在的cpu对内存有关,当代码内存过大,运行代码会找不到,则重新加载你的代码信息

inline到底会不会展开还是看编译器,这只是标识告诉编译器我需要展开

mutable

在类定义成员的时候用到

namespace

命名空间

以及命名空间的指示

命名空间也可不写名字

noexcept(c++11起)

明确告诉编译器不抛出异常,编译器则采用相关优化方法

如果抛出了异常则调用如下

1
2
3
4
5
std::terminate();
abort();
exit(0);

//noexcept(false)可以放参数,默认也是false,表示也可能会抛出异常

not

代表键盘!

not_eq

等价于!=

or

等价于||

or_eq

等价于|=

nullptr(c++11起)

用来专门代表空指针

1
如果传0无法推到,必须转换成(int*)0的参数

operator

定义类的时候重载之时改变一些+,-,||等符号的重载,对于库的编辑者非常方便,但是对应用开发几乎不怎么使用

应用开发使用operator带来的坏处可能还多点

这类重载就会带来一些问题

但是如果使用下面的方式将考虑重载=号

如果重载了”+”号,那“+=”符号没重载就很奇怪,也一并去重载掉

register

希望数据放入寄存器中,编码基本不用

reinterpret_cast

意图长的就少用,转换类型的一种

  • static_cast 类型转换 //float转int等

  • const_cast 破坏const语义

  • dynamic_cast 父子指针转换,并且转换失败会返回0

  • reinterpret_cast 指针转换

  • c_like_cast

    1
    2
    3
    4
    //万能转换
    //直接转换比如
    int c = 10;
    char a = (char)c;

c++希望在合适的场景运用合适的cast做类型转换,而不是直接用c_like_cast

requires

c++17中会引用

sizeof

  • sizeof 对数组,得到整个数组所占空间大小。
  • sizeof 对指针,得到指针本身所占空间大小。
1
2
3
sizeof(void);//不允许
sizeof(int[]);//不允许
sizeof(bit.bit);//不占1个字节也不行

static

  1. 修饰普通变量,修改变量的存储区域和生命周期,使变量存储在静态区,在 main 函数运行前就分配了空间,如果有初始值就用初始值初始化它,如果没有初始值系统用默认值初始化它。

  2. 修饰普通函数,表明函数的作用范围,仅在定义该函数的文件内才能使用。在多人开发项目时,为了防止与他人命令函数重名,可以将函数定位为 static。

  3. 修饰成员变量,修饰成员变量使所有的对象只保存一个该变量,而且不需要生成对象就可以访问该成员。
  4. 修饰成员函数,修饰成员函数使得不需要生成对象就可以访问该函数,但是在 static 函数内不能访问非静态成员。

static_assert(c++11起)

assert断言,用的多是对自己的代码严格

当assert不满足值就终止掉,但static_assert区别会在编译器的时候就抛出问题来,而且处理的问题也是编译器中

而不允许在运行期间使用

static_cast

四种cast的方式,同等类型转换,int转long 转char,之类的转换

thread_local(c++11起)

方便多线程编程的时候,多线程的数据不一致类似java的thread_local

typedef

把一个很长的类型换个短名字

typeid

为了增加运行期的查看类型引入的关键词

typename

union

using

类似typedef的功能

volatile

不要内存优化,存取直接拿主内存,类似Java的volatile

重点领域

前置申明

前置声明的对象最好用指针赋值,因为不知道具体的数据大小

上面的情况只有E可作为前置声明省略头文件引入

三个基本原则

  • 析构函数函数重写
  • 拷贝构造函数重写
  • =号拷贝函数重写

c++中有析构函数用来回收资源

析构函数

系统默认的析构函数,默认的调用类成员的析构函数,如果类成员是派生,则使用派生成员的析构函数

但是如果是类成员是内置类型的,则类成员的析构函数是什么都没做

所以还是得通过代码手动delete 资源掉

构造拷贝,等号拷贝

如果提供了=号拷贝,构造拷贝功能,那也要明确的写出拷贝的构造函数

默认的拷贝只是浅拷贝,使用析构的时候内存会重复释放

当程序不想要等号拷贝和构造拷贝,那就单纯的声明出来即可,不需要写实现代码进去

或者c++11的写法后面追加delete关键字

左右值引用

在函数处理传参的时候都是值拷贝,如果普通的数据变量进去都无法修改原先的数据
要修改原先的数据则需要放入指针,而指针也是开辟内存空间浪费
所以c++引出了右值引用,直接将数据的引用传参过去,既能改变数据又能不开辟指针空间

1
2
3
4
5
// 在函数处理传参的时候都是值拷贝,如果普通的数据变量进去都无法修改原先的数据
// 要修改原先的数据则需要放入指针,而指针也是开辟内存空间浪费
// 所以c++引出了引用,直接将数据的引用传参过去,既能改变数据又能不开辟指针空间
int& e = a;
//最普通的e就是一个引用,给a起了别名操作

右值引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int a = 0;
// a是左值, 0是右值
int b;
// b是左值
int c = 2 + 3;
// C是左值, 5是右值有值没名字
int d = a + c;
// d是左值, a+c是右值且有值但是没有名字
auto address = &a;
// 能取到地址的值称为左值,右值不能取地址

// 在函数处理传参的时候都是值拷贝,如果普通的数据变量进去都无法修改原先的数据
// 要修改原先的数据则需要放入指针,而指针也是开辟内存空间浪费
// 所以c++引出了引用,直接将数据的引用传参过去,既能改变数据又能不开辟指针空间
int& e = a;
//最普通的e就是一个引用,给a起了别名操作

//右值引用
int&& re = 2;
// 原先2不能取引用,但是右值引用可以即双&符,但是如果是const int &也可以取2数值

std::move会让构造拷贝右键引用显得不一样

1
2
3
4
5
//构造函数的T&& rhs
T b = std::move(a)

//如果只是普通的右值赋值则是(const T & rhs)的构造函数
T c = a;

构造抛异常

析构函数不要抛出异常,默认的时候也加了noexcept

如果要捕获析构函数异常那就加false选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class EvilB{
public:
~EvilB() noexcept(false) {
throw std::string("EvilB data error");
}
};

int test(){
try {
EvilB b;
// throw std::string("error");
} catch (std::string const &e){
std::cout << e << std::endl;
}
}

int main(){
test();
}

但是如果是派生类情况就复杂了

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 A{
public:
~A() noexcept(false){
std::cout<< "Bye A" << std::endl;
throw std::string("A data error");
}
};

class EvilB: public A{
public:
~EvilB() noexcept(false) {
throw std::string("EvilB data error");
}
};

int test(){
try {
EvilB b;
// throw std::string("error");
} catch (std::string const &e){
std::cout << e << std::endl;
}
}

int main(){
test();
}

//Bye A
//程序挂了 core dumped

所以希望析构函数的时候别抛出异常,程序处理不了这样的情况

但是普通的函数调用一旦抛出异常的情况下,会捕捉异常然后catch后继续往后走,析构则不会

虚析构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Base *info = new Drived(1);
Grouped *group = new Grouped(2);
Event ev;

//group能调用的func
group->print();
group->act(ev);
group->addBase(info);
group->removeBase(info->id());
group->id();

//由派生类转基类能运用的也只是基类的func
Base* baseGroup = group;
baseGroup->act(ev);
baseGroup->print();
baseGroup->id();
//baseGroup->addBase(info);
//baseGroup->removeBase(info->id());


delete info;
delete baseGroup;
//delete group; 因为虚构的话等价调用

如果不是虚析构函数,那么这里析构执行的只是Base的析构函数

而不执行Group的析构函数

不使用派生类的function的方法

或者自身内部调用

在构造函数和析构函数调用虚函数,会调用自身的虚函数

这个很好理解,构造和析构函数调用虚函数,不会调用派生类上的虚函数而是调用自身的虚函数上,以析构来举例,基类的析构调用虚函数,派生类之前就调用过析构已经删除了资源,没有意义再去调用派生类的虚函数了

如果你的成员里有虚函数或者纯虚函数,则需要将public的析构函数置为虚函数

又或者把你的虚构函数隐藏起来

你如果把析构函数隐藏的话,不能使用如下

1
2
3
Base b;//这样的语句是非法的
Base *b = new Base;//这样可执行,但是不能delete
delete b;//这会出现问题

虽然析构函数protected,但是可以在派生类去操作

如下的操作将会导致无法释放相关的资源,因为是Base的指针

auto关键字

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
for(auto v:ids){
//V只是一个值
}

for(int i=0; i < sizeof(ids) / sizeof(ids[0]); i++){
// ids[i]是指针
}

//从效率上
//for(auto v: group)最快
//for(size_t i=0; i< group.size(); i++) 效率第二
//for(std::vector<int>::const_iterator iter= group.begin();
// iter != group.end(); ++iter) 效率最慢
// 如果iter++这样的写法是先保留iter再加加,做了一步无用功,直接加加返回即可

//因为做了iter的判断其实和第二个效率差不多,做的事情都多一些
//而auto是最快的,编译器会做优化

for(size_t i=0, size=group.size(); i<size;i++){
//这个稍微快一点
}


for(auto& v:group){
//v可作为引用
v= v*v;//这样就会影响group内部的变量本身
}

for(const auto& v:group){
//所以为了误操作,得用const引用
}

提前将bean和end取出来

构造

构造相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A{
public:
explicit A(int value):m_value(value){}
private:
int m_value;
}

A a(10); //圆括号
A b{10}; //花括号
//花括号用来描述以下场景
std::vector<int> ba = {1, 2, 3};
std::vector<int> bb{1, 2, 3};
//以前的写法都是如下的
int avector[] = {1, 2, 3}; //先定义数组
std::vector<int> bc;//再定义vector
for(auto v:avector)bc.push_back(v); //再push_back一个个塞进去

//精度丢失
A c(1.1);

默认参数

NewB在默认值的直接写到private上,做了这么个优化

在老版本上m_weight就会出现65535的错误值

左值引用,右值引用

引用和指针稍微像,但是引用不可能是空的,像给其他变量起了别名,引用还不用开辟指针需要的内存空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int a = 10;
int& refA = a;
const int& constRefA = a;
//refA和constRefA差不多功能,只是带了一个不可变的const

const int& constRefB= 10;
//对于一个右值引用,可直接采用不可改变的引用

//这个函数不管左值引用还是右值引用都可以给参数赋值
void printInfo(const int& a){
std::cout << a << std::endl;
}

printInfo(a)
printInfo(refA);

auto&& rra = 10;
//rra 是一个右值引用,只能指向int类型
//没有&& 只能用const auto& rra = 10;
rra = 30; //而且能改变大小

内存管理知识

malloc,传入参数需要多大内存,返回值是void*,可以指向任何东西

free,释放对应的指针内存

但是在C++强类型的,malloc返回的是void*,不符合c++规则,而且返回的只是开辟内存空间未经过初始化的构造函数

所以对应c++得有直接的分配内存和资源的方法

new 分配资源调用构造函数

delete 调用析构 释放内存

但是最大的弊端,new忘了delete很容易犯错,设计上就有这个缺陷,程序员很难做出是否要释放的需要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const char* getName(){
static char valueGroup(1000);
return valueGroup;
}

const char* getName2(){
char* value = (char*)malloc(1000);
return value;
}

//这种函数调用者就好奇要不要自己来管控内存进行delete free
//或者第一种在多线程的情况下也会有问题

char* getName(char*v ,size_t bufferSize){
return v;
}

//delet [] buffer

//异常流抛出,程序如果跳出去
badThing();
//有些以下的代码就执行不了了,就会出现内存泄漏

引入了异常流之后,对内存的管控就更加乱,也很容易出现泄露

为解决内存泄漏,科学家想到的版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class SafeIntPointer{
public:
explicit SafeIntPointer(int v):m_value(v){

}
~SafeIntPointer(){
m_used --;
if(m_used <= 0)
delete m_value;
}
int* getValue(){
return m_value;
}
private:
//但是当资源被很多方使用,等最后那个调用方删除才有效,所以增加引用计数
int m_used;
int *m_value;
}

通过简单的构造和析构,把newdelete给解决掉

SaleIntPointer在单线程虽然正确但是在多线程就出现了问题

因为多线程的情况下,没有同步会多重执行delete m_value

到了c++11的情况下

就有了智能指针std::shared_ptr来管理这个情况了,就不会有这个问题了

shared_ptr常规使用

c++98的auto_ptr不推荐使用,未来的标准还可能要去掉这个

  • shared_ptr:每增加一次引用加一,做到指针共享
  • unique_ptr:独占,一个指针必须只有一个使用者来使用,不能拥有两个使用者
  • weaked_ptr:与shared_ptr搭配使用

构建一个Object类型的类

1
typedef std::shared_ptr<Object> ObjectPtr;

obj3.swap(obj4)//obj3与obj4交换管理的资源或者std::swap(obj3, obj4)

智能指针后期直接用get拿取真实对应的指针资源进行操作

也可以直接拿裸指针用,因为指针指针重载->,*

可通过以下方式减小引用计数

  • reset
  • obj2 = nullptr

以下可以查询指针是否单独一个调用方使用

如果对智能指针当做函数参数值传入,那么也会对引用+1

所以采用const 引用的方式传入

1
2
3
void printRef(const ObjectPtr& obj){
std::cout << obj.use_count() << std::endl;
}

智能指针也可进行专门放入析构的时候清理函数

weak_ptr常规使用

1
2
3
4
5
6
7
//测试代码
void testParentAndChild(){
ParentPtr p(new Parent());
ChildPtr c(new Child());
p->son = c;
c->father = p;
}

输出结果是什么都没输出

该两个析构函数无法调用到

在这里有严重明显的内存泄漏

这时候构造函数再输出一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//输出结果,构造函数调用到了

//hello parent
//hello child

//用智能指针做管理的确会有问题
//Parentptr初始化p智能指针,use_count=1
//ChildPtr初始化c智能指针,use_count=1

p->son=c;// C.use_count() == 2, p.user_count()==1
c->father=p;// c.use_count() == 2, p.use_count() ==2

//p析构的时候只是将c的use_count减1
//c析构的时候只是将p的use_count减1

//两份资源并没有释放掉

通过weak_ptr可以打破这种关系

1
2
3
4
5
6
7
8
9
10
11
typedef std::weak_ptr<Object> WeakobjectPtr;

//弱指针,当指针外部所有的智能指针都不管理则无效,有效无效依赖于外部其他智能指针

//要使用的时候做一个转换
auto p = weakObj.lock();//当调用lock瞬间,如果指向的资源,外部有其他智能指针,不等0的情况就返回一个有效指针,0返回空指针, 否则返回资源指针
if(p){
//true返回,但是unique是false,所以在true的情况下,p.use_count() >=2的
p.unique();
}
//WeakObjectPtr不管如何引用都是use_count:1

当obj调用reset,所有的调用都直接返回空指针

expired

查询是否过期

查询外部有没有管理一份资源

之前的内存泄漏代码就修改如下

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
74
75
76
77
78
79
80
81
82
83
84
85
86
#include "SharedPointer.h"
#include <iostream>
class Parent;

typedef std::shared_ptr<Parent> ParentPtr;
typedef std::weak_ptr<Parent> WeakParentPtr;
class Child{
public:
WeakParentPtr father;
~Child();
Child();
void checkRelation();
};

typedef std::shared_ptr<Child> ChildPtr;
typedef std::weak_ptr<Child> WeakChildPtr;

class Parent{
public:
WeakChildPtr son;
~Parent();
Parent();
void checkRelation();
};


//智能指针方式传入
void handleChildAndParent(const ParentPtr& p, const ChildPtr& c){
//father和son都是waek_ptr都是先lock提取后get提取
//shared_ptr都是直接get提取
auto cp = c->father.lock();
auto pc = p->son.lock();
if(cp == p && pc == c){
std::cout<< "right relation\n";
} else {
std::cout << "oop !!!!!\n";
}
}
//引用的方式传入
void handleChildAndParentRef(const Parent& p, const Child& c){ //father和son都是waek_ptr都是先lock提取后get提取
//shared_ptr都是直接get提取
auto cp = c.father.lock();
auto pc = p.son.lock();
if(cp.get() == &p && pc.get() == &c){
std::cout<< "right relation\n";
} else {
std::cout << "oop !!!!!\n";
}
}

Child::Child(){std::cout<< "hello Child" << std::endl;}
Child::~Child(){std::cout<< "bye Child" << std::endl;}
Parent::Parent(){std::cout<< "hello Parent" << std::endl;}
Parent::~Parent(){std::cout<< "bye Parent" << std::endl;}

void Child::checkRelation(){

}

void Parent::checkRelation(){
auto ps = son.lock();
if(ps){
//不靠谱的做法就是如下,this转智能指针作为参数传过去
//this:当出了作用域,智能指针P将会调用智能指针析构函数
//p指针将会变成0,管理的指针然后也将会被delete
ParentPtr p(this);

handleChildAndParent(p, ps);
}
}

int main(){
ParentPtr p(new Parent());
ChildPtr c(new Child());
p->son = c;
c->father = p;
p->checkRelation();
}

//输出
//hello parent
//hello child
//right relation
//bye parent 可疑调用点
//bye child
//bye parent

enable_shared_from_this CRTP 奇异递归模板模式

enable_shared_from_this CRTP

bye parent调用了两次析构函数

1
2
ParentPtr p(this);	
//这一块出作用域的时候调用了析构函数

对象成员属性里有智能指针管理的类型,要传出或者调用的时候

要将对象从enable_shared_from_this派生

从而将

1
2
3
4
5
6
7
8
9
10
11
12
//ParentPtr p(this);	
//这一块出作用域的时候调用了析构函数

//修改成如下
shared_from_this();//作为调用指针

//输出结果
//hello Parent
//hello Child
//right relation
//bye Child
//bye Parent

但是希望不要直接Parent pp;使用出来,因为此类本就派生了enable_shared_from_this是给智能指针来使用的

unique_ptr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef std::unique_ptr<Object> UniqueObjectPtr;

// unique
UniqueObjectPtr obj(new Object(30));
auto ptr1 = obj.get(); //重载了operator -> *等操作,导致其功能直接使用,不需要get
if (ptr1){//重载了 operator bool
std::cout << ptr1->getValue() << std::endl;
print(ptr1);
ptr1.release();
}
//unique 是唯一的,在某一个特定的时刻,只保证一个使用的智能指针
//c++98的拷贝,拷贝构造函数,等于符号在unique是不存在的


void print(const UniqueObjectPtr& obj){

}

release

unique_ptr的release并不是释放资源,而是将指针的控制权转移给其他智能指针了

reset

1
2
ptr1.reset();//	不带任何参数,把以前管理的资源调用析构然后delete
ptr1.reset(new Object(2));//如果传入新的指针就是把原有的资源释放掉,再去管理新的指针

C++11引入了右值和右值引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//unique_ptr,在同一时刻只能由一个指针管理资源

void transfer(UniqueObjectPtr obj){
std::cout << obj.id() << std::endl;
}

//UniqueObjectPtr(const UniqueObjectPtr &) = delete
//UniqueObjectPtr(UniqueObjectPtr&&) = default

int main(){
//......
transfer(std::move(ptr1));
std::cout << ptr1.id() << std::endl;
//如果普通作为形参资源,对原有的ptr1就不能够掌握了,其实ptr1就是空指针
//需要assert(obj == nullptr);
//通过std::move(ptr1)
}

所以要将unique_ptr进行转移

通过普通传参然后release并且delete

或者std::move()传参

c++11大多数来源于在boost库也方法,还有一个scoped_ptr,类似于unique_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
//不要自己手动管理资源,即不要出现new和delete关键字,malloc&free关键字
int *a = new int(10);
delete a;
int *b = malloc(sizeof(int));
if (b){
free(b);
}
//要交给专门做管理的智能指针


//一个裸指针不要用两个shared_prt管理,unique_ptr也一样
auto pObj = new Object(1);
ObjectPtr obj(pObj);
ObjectPtr obj2(pObj);
//两个sharedPtr没有任何关联关系
//两个uniquePtr也没有任何关联关系


//用weakPtr可以打破循环引用,parent和child

//当需要在类内部接口中,如果需要将this作为指针,可通过enable_shared_from_this操作

//使用shared_ptr作为函数接口,如果有可能则使用const shared_ptr&的形式

//shared_ptr weak_ptr和裸指针相比,会大很多,并且效率(空间、时间)也有影响,尤其多线程模式下
//一个裸指针的空间大小是4个字节32位计算机,64位是8个字节
//shared_ptr的空间大小
//64位计算机上管理指针最小也得8个字节,第二shared_ptr是引用计数的,所以需要存引用计数
//第三shared_ptr配合weak_ptr打破循环引用,所以shared_ptr也要存入一个数记录多少个weak_ptr用到我了
//所以总结两个计数一个裸指针,所以最起码也得24个字节3个裸指针,但是实际上用到的空间更加多


ObjectPtr obj5 = std::make_shared<Object>(3);
//等价于ObjectPtr obj5(new Object(3));
//以上的new 可以一步到位,new的object+智能指针所需,少了一步new Object(3),少了8个字节
//减少了new和delete的开销


//enable_shared_from_this和shared_from_this()在构造和析构函数里不能调用,会直接出错


//智能指针的优先级在于 普通指针 -> unique_ptr -> shared_ptr

lambda

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
void printInfo(int a, int b, int c){
std::cout << "a " << a << "b " << b << "c " << c << std::endl;
}

struct Print{
void operator()[int a, int b, int c] const{
std::cout << "a " << a << "b " << b << "c " << c << std::endl;
}
}

template<typename T1, typename T2, typename T3>
void templatePrint(T1 a, T2, b, T3 c){
std::cout << "a " << a << "b " << b << "c " << c << std::endl;
}

struct TemplatePrint{
template<typename T1, typename T2, typename T3>
void operator()[T1 a, T2 b, T3 c]{
std::cout << "a " << a << "b " << b << "c " << c << std::endl;
}
}


int main(){
Print printUseClass;
TemplatePrint printUseTempClass;

printInfo(1, 2, 3);//普通函数
printUserClass(1, 2, 3);//定义了Print类对象,重载了operator()可以当做函数使用
templatePrint(1, 2, 3);//普通函数,但参数模板化了
printUseTempClass(1, 2, 3);//定义了TemplatePrint类对象,重载了operator()
}

lambda c++11,将一个inner函数的定义作为参数或者local对象

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
inline void print(int a, int b, int c){
std::cout << "a " << a << "b " << b << "c " << c << std::endl;
}

template<typename Func>
void printUseFunc(Func func, int a, int b, int c){
func(a, b, c);
}

int main(){
auto local = [](int a, int b, int c){
std::cout << "a " << a << "b " << b << "c " << c << std::endl;
}//lambda本质上是一个inline函数,只是用了一些语法糖

local(1, 2, 3);

printUserFunc([](int a, int b, int c){
std::cout << "a " << a << "b " << b << "c " << c << std::endl;
}, 1, 2, 3);
//参数形式的lambda

auto local2 = [a, b, c](){
....
}
local2();
//外面的变量中a,b,c按值拷贝传入

auto local3 = [=](){
.....
}
local3();
//外面的变量全部按值拷贝

auto local4 = [&](){
...
}
local4();
//外面的变量按引用全部传入到具体的lambda函数内部
}