指针、引用、const、类的浅显理解
一、引用
参考C++ primer 5th中文版 Page 45.
定义引用时程序把引用和它的初始值bind在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始值对象一直绑定在一起。因此无法令引用重新绑定在另一个对象上。
引用为对象起了一个另外的一个名字
注意:
引用并非对象,相反的,它只是为一个已经存在的对象所起的另外一个名字
引用必须初始化
1.1 理解与实验 和const
初始化后不可以重新绑定对象,相当于一个 const pointer to Object, Object是不是const看是否有const的修饰
1.1.1 实验一
reference is a const pointer to Object
#include <iostream>
#include <assert.h>
using namespace std;
int main(int argc, char **argv)
{
int init_a = 2;
int init_b = 3;
int &a= init_a;
int &b=init_b;
a=6;
std::cout<<"init_a="<<init_a<<" a= "<<a<<std::endl;//init_a=6 a= 6
a=b;//不能理解为引用的重新绑定对象 而是通过b修改了a和init_b的值
std::cout<<"init_a="<<init_a<<" a= "<<a<<std::endl;//init_a=3 a= 3
int init_c =10;
//&a=init_c;//编译错误
return 0;
}
类型什么的肯定是要严格匹配的,如int对int;double对double
1.1.1 实验二
Object是不是const看是否有const的修饰
#include <iostream>
using namespace std;
int main(int argc,char**argv)
{
int init_a = 0;
int init_b = 1;
const int & a=init_a;
init_a = 10 ;
cout<<"init_a = "<<init_a<<" a = "<<a<<endl;//init_a = 10 a = 10
//a = 100;//编译错误 error: assignment of read-only reference ‘a’
const int ci=1024;
const int &r1 = ci; //正确
//r1 = 42;//error: assignment of read-only reference ‘r1’
//int &r2 = ci;//error: binding reference of type ‘int&’ to ‘const int’ discards qualifiers 试图将一个非常量引用指向一个常量对象
return 0;
}
看出const int & a=init_a;
表示的是一个const pointer to const Object表示不可以通过a去修改,但由于init_a不是定义为const所以 =10赋值修改可行
见
C++ Primer 5th 中文版 Page 56:
和常量引用一样,指向常量的指针也没有规定其所指的对象必须是一个常量。所指向常量的指针仅仅要求不能通过该指针改变对象的值,而没有规定那个对象的值不能通过其他途径改变
下面还有一句Tips
试试这样想吧:所谓指向常量的指针和引用,不过是指针或引用“自以为是”罢了,他们觉得自己指向了常量,所以自觉地不去改变所指对象的值
二、指针
指针的本质
例如
int a = 1;
int * p =&a;
指针是指向另外一种类型的复合类型
指针是一个对象,允许对其赋值和拷贝,在指针的生命周期可以先后指向不同的对象(对指针对象本身可以取得地址)
指针无需在定义时赋予初值,和其他内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值
在C++ Primer 5th 中文版 Page 49建议初始化所有的指针
空悬指针等
指向一个对象
指向紧邻对象所占空间的下一个位置
空指针
无效指针上述情况的其他值
三、指针和引用的区别
编程指北注:其第七条有误
别名
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int &b = a;
b++;
std::cout << "a= " << a << ",b=" << b << std::endl;
//a= 11,b=11
}
指针和引用的自增(++)和自减含义不同,指针是指针运算, 而引用是代表所指向的对象对象执行++或–
参考C++ primer 5th中文版 Page 45.
定义引用时程序把引用和它的初始值bind在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始值对象一直绑定在一起。因此无法令引用重新绑定在另一个对象上。
引用为对象起了一个另外的一个名字
注意:
引用并非对象,相反的,它只是为一个已经存在的对象所起的另外一个名字
引用必须初始化
四、const(const和pointer)
pointer分两种const或者not const,对于const修饰的指针和引用见1.1.1 实验二
4.1 pointer to const
指向常量指针(pointer to const)不能用pointer改变其所属对象的值。要想存放常量对象的地址,只能使用指向常量的指针
同样有些注意见1.1.1 实验二:
#include <iostream>
#include <vector>
using namespace std;
int main(int argc,char**argv)
{
const double pi=3.14;
//double *ptr = π//error: invalid conversion from ‘const double*’ to ‘double*’ [-fpermissive]
const double *cptr = π
//*cptr = 42;//error: assignment of read-only location ‘* cptr’
return 0;
}
4.2 const pointer to Object
const pointer指指针本身是常量,它必须初始化,即里面存储的地址就不能再改变了 (即不变的是指针本身的值而并非指向的那个值)
#include <iostream>
#include <vector>
using namespace std;
int main(int argc,char**argv)
{
int errNumber = 0;
int * const curErr = &errNumber;//curErr将一直指向errNumb
const double pi=3.1415926;
const double *const pip = π//pip是一个指向常量对象的const pointer
return 0;
}
4.3 顶层const和底层const
顶层const:指针本身是一个常量
底层const:指针所指的对象是一个常量
分清:从右向左读
const int p;
const int* p;
int const* p;
int * const p;
const int * const p;
int const * const p;
4.3.1 实验
#include <iostream>
#include <vector>
using namespace std;
int main(int argc, char **argv)
{
/**
* @brief 从右向左读
*
*/
//1. const int p;常量
//const int p=10;
// //2. p is a pointer to const:一个指向常量的指针
// const int *p;
// const int a = 1;
// const int b = 10;
// int c = 100;
// p=&a;
// //*p=100;//error: assignment of read-only location ‘* p’ 即指向的对象不可更改
// p=&b;
// std::cout<<*p<<std::endl;//10-->说明指向可更改(即指针对象里面存的指向的对象地址可以更改)
// p=&c;//c虽然不是常量但是也成立因为指针认为只管自己 C++ Primer 5th 中文版 Page 56
//3. p is a pointer to const:一个指向常量的指针 //同上
// int const *p;
// const int a = 1;
// const int b = 10;
// int c = 100;
// p=&a;
// *p=0;//错误 不能通过指针去更改指向的对象的值
// p=&b;
// p=&c;
// 4. p is a const pionter to int//一个const pointer指向int
//int *const p;//error-->必须初始化
//int a = 1;
//int b = 10;
//int const c = 100;
//int *const p=&a;
//p=&b;//error: assignment of read-only variable ‘p’ p的值作为常量不可更改(即指向的对象不可更改)
//*p = 10;//正确,因为int 不被const修饰
//int *const p2=&c;//类型不对 a value of type "const int *" cannot be used to initialize an entity of type "int *const"C/C++(144)invalid conversion from ‘const int*’ to ‘int*’ [-fpermissive]
// 5. p is a const pointer to const int// 一个 const pointer指向const int
//const int *const p;//error-->必须初始化;
// const int a = 1;
// const int b = 10;
// const int *const p = &a;
// //*p=100;//error: assignment of read-only location ‘*(const int*)p’ 指向的对象是常量
// //p=&b;//error: assignment of read-only variable ‘p’ 指针本身是常量(指针对象存的地址不可更改)-->指针指向不可更改
// int c = 100;
// const int *const p2 = &c;//c虽然不是常量但是也成立因为指针认为只管自己 C++ Primer 5th 中文版 Page 56
// //*p2=1000;//error: assignment of read-only location ‘*(const int*)p2’
// c=10000;
// 6. p is a const pointer to const int // 同上 //一个 const pointer指向const int
// int const *const p;
return 0;
}
五、空悬指针、野指针、无效指针
空悬指针(dangling pointer):指向一块曾经保存数据对象但现在已经无效的内存的指针
野指针:不确定指向的指针,常常来自未初始化的指针(野指针可能对正常数据产生影响),所以指针使用时一定建议初始化
无效指针:
class A;
A* p = new A();
A* q = p;
delete p;
//pointer q is invalid now.
六、拷贝(深拷贝、浅拷贝)
6.1 浅拷贝
在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数-----浅拷贝函数—一一赋值(operator()=)
即直接A=B
没有重新分配新内存
class Person{
public:
Person(int _age):age(_age)
{
}
int age;
int *ptr = new int;
~Person()
{
delete ptr;
}
};
上述代码调用拷贝构造时会double free问题
因为浅拷贝按值赋值两个 对象的ptr成员指向同一块内存 当一个对象(p2)析构时,销毁 delete p2.ptr 指向的内存
由于指向同一块内存的指针 再次delete会导致double free
6.2 深拷贝
不止直接A=B的直接赋值
class Person
{
public:
int age;
int *ptr = new int;
Person(int _age) : age(_age)
{
}
Person(Person const &p)
{
this->age = p.age;
this->ptr = new int(*p.ptr);
}
Person &operator=(Person const &p)
{
this->age = p.age;
this->ptr =new int(*p.ptr);
return *this;
}
~Person()
{
delete ptr;
}
};
int main(int argc, char **argv)
{
Person p1(10);
Person p2(20);
p1 = p2;
std::cout << p1.age << std::endl;
std::cout << *p1.ptr << std::endl;
p1.age = 40;
p2.age = 50;
std::cout << "p1 age= " << p1.age << " p2 age =" << p2.age << std::endl;
return 0;
}
上述写的有点问题this->ptr原来指向的对象没有被释放
#include <iostream>
using namespace std;
class Person
{
public:
int age;
int *ptr = new int;
Person(int _age) : age(_age)
{
}
Person(Person const &p)
{
this->~Person();
this->age = p.age;
this->ptr = new int(*p.ptr);
}
Person &operator=(Person const &p)
{
this->~Person();
this->age = p.age;
this->ptr =new int(*p.ptr);
return *this;
}
~Person()
{
delete ptr;
}
};
int main(int argc, char **argv)
{
Person p1(10);
Person p2(20);
p1 = p2;
std::cout << p1.age << std::endl;
std::cout << *p1.ptr << std::endl;
p1.age = 40;
p2.age = 50;
std::cout << "p1 age= " << p1.age << " p2 age =" << p2.age << std::endl;
return 0;
}
上述进行深拷贝就避免了double free ---->虽然还没完美符合三五法则
6.3 一些注意
c++里面的大多数实现的对象都是进行的拷贝是深拷贝的如vector等(这里值得是编译器提供好的对象)(里面不存放指针)
其实就是按=(赋值)直接拷贝
但是shared_ptr和weak_ptr实现的是浅拷贝
unique_ptr禁止拷贝
6.4 三五法则(*)
图片来自b站小彭老师:RAII与智能指针
七、vector等容器存放指针数据的释放问题
std::vector<Object*> //该类型虽然是STL容器,但存储了不安全的原始指针
7.1 释放时出现double free
#include <iostream>
#include <map>
#include <vector>
#include <deque>
#include <assert.h>
#include <memory>
using namespace std;
class Person
{
public:
Person(int _age) : age(_age)
{
}
int age;
};
int main()
{
std::vector<Person*> people;
auto p_shared =new Person(2000);
people.push_back(new Person(10));
people.push_back(new Person(20));
people.push_back(new Person(30));
people.push_back(p_shared);
people.push_back(p_shared);
std::cout << people.size() << std::endl;
for(auto p:people)
{
std::cout <<"p is "<<p<<std::endl;
}
for(auto iter= people.begin();iter!=people.end();++iter)
{
if(*iter!=nullptr)
{
delete (*iter);
*iter =nullptr;//最后一个出现了问题 free(): double free detected in tcache 2// TODO
}
}
people.clear();
return 0;
}
数值里面的数据打印出来是
p is 0x55555556ced0
p is 0x55555556cf10
p is 0x55555556cef0
p is 0x55555556ceb0
p is 0x55555556ceb0
最后两个一样当第四个进行释放 第五个再次进行释放的时候 该指针指向的地址已经释放造成double free
7.2 先考虑people[0]的释放
#include <iostream>
#include <map>
#include <vector>
#include <deque>
#include <assert.h>
#include <memory>
using namespace std;
class Person
{
public:
Person(int _age) : age(_age)
{
}
int age;
};
int main()
{
std::vector<Person*> people;
auto p_shared =new Person(2000);
people.push_back(new Person(10));
people.push_back(new Person(20));
people.push_back(new Person(30));
people.push_back(p_shared);
people.push_back(p_shared);
delete people[0];
std::cout << "people's size is "<<people.size() << std::endl;
for(auto p:people)
{
std::cout <<"p is "<<p<<std::endl;
}
std::cout<<people[0]<<std::endl;
std::cout<<people[0]->age<<std::endl;//没错但对象已经回收了内存回收了 1431752496 指向一个混乱的数据 空悬指针
people[0]=nullptr;
std::cout<<people[0]<<std::endl;
std::cout<<people[0]->age<<std::endl;//Segmentation fault 空指针的非法操作
return 0;
打印为
people's size is 5
p is 0x55555556ced0
p is 0x55555556cf10
p is 0x55555556cef0
p is 0x55555556ceb0
p is 0x55555556ceb0
0x55555556ced0
1431752496
0 //people[0]=nullptr;
所以
if(people[0]!=nullptr)
{
//一些操作
}
若果调用people.clear()虽然不报错,但并不是真正的释放内存
/*
Erases all the elements. Note that this function only erases the
elements, and that if the elements themselves are pointers, the
pointed-to memory is not touched in any way. Managing the pointer is
the user's responsibility.
*/
这种情况下vector
people.clear();
for(auto p:people)
{
std::cout <<"p is "<<p<<std::endl;
}
std::cout<<"after clear "<<endl;
std::cout<<people.size()<<std::endl;//输出的是0
return 0;
std::cout<<people.capacity()<<std::endl;//8
people.clear();
std::cout<<people.capacity()<<std::endl;//8
见a和b
解释 这些指针指向的对象不会被销毁
并且还有4和5指向同一块内存的东西
7.3 利用智能指针
#include <iostream>
#include <map>
#include <vector>
#include <deque>
#include <assert.h>
#include <memory>
using namespace std;
class Person
{
public:
Person(int _age) : age(_age)
{
}
int age;
};
int main()
{
std::vector<std::shared_ptr<Person>> people;
auto p_shared = std::make_shared<Person>(Person(2000));
people.push_back(make_shared<Person>(Person(20)));
people.push_back(make_shared<Person>(Person(20)));
people.push_back(make_shared<Person>(Person(20)));
people.push_back(p_shared);
people.push_back(p_shared);
std::cout << people.size() << std::endl;
for (auto p : people)
{
std::cout << "p is " << p << std::endl;
}
for (auto iter = people.begin(); iter != people.end(); ++iter)
{
if (*iter != nullptr)
{
(*iter).reset();
}
}
for (auto p : people)
{
std::cout << "p is " << p << std::endl;
}
assert(people[0]==nullptr);
std::cout<<"end "<<std::endl;
std::cout<<people[0]->age<<std::endl;//Segmentation fault 因为是nullptr
people.clear();
return 0;
}
/*
5
p is 0x55555556fee0
p is 0x55555556ff20
p is 0x55555556ff00
p is 0x55555556fec0
p is 0x55555556fec0
p is 0
p is 0
p is 0
p is 0
p is 0
end
*/
引用计数为0,能够安全的释放
7.4 两个vector里面存放指针(进行a=b的拷贝操作)
7.4.1 普通指针
代码
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
class Person
{
public:
Person(int _age) : age(_age)
{
}
int age;
};
int main(int argc, char **argv)
{
std::vector<Person *> people_a;
people_a.push_back(new Person(10));
people_a.push_back(new Person(20));
people_a.push_back(new Person(30));
people_a.push_back(new Person(40));
auto people_b = people_a;
std::cout << "people_a is......" << std::endl;
for (auto &p : people_a)
{
std::cout << "p is " << p << std::endl;
}
std::cout << "people_b is......" << std::endl;
for (auto &p : people_b)
{
std::cout << "p is " << p << std::endl;
}
for (auto &p : people_a)
{
if (p != nullptr)
{
delete p;
p = nullptr;
}
}
for (auto &p : people_b)
{
if (p != nullptr)
{
delete p;
p = nullptr;
}
}
return 0;
}
/*
输出
people_a is......
p is 0x55555556ceb0
p is 0x55555556cef0
p is 0x55555556ced0
p is 0x55555556cf10
people_b is......
p is 0x55555556ceb0
p is 0x55555556cef0
p is 0x55555556ced0
p is 0x55555556cf10
//这样会造成double free的---->free(): double free detected in tcache 2
*/
std::vector的深拷贝:数组重新开辟内存,里面的元素直接复制(直接复制指针)delete时另一个会造成double free的
7.4.2 智能指针
#include <iostream>
#include <vector>
#include <memory>
#include <assert.h>
using namespace std;
class Person
{
public:
Person(int _age) : age(_age)
{
}
int age;
};
int main(int argc, char **argv)
{
std::vector<shared_ptr<Person>> people_a;
people_a.push_back(make_shared<Person>(Person(10)));
people_a.push_back(make_shared<Person>(Person(20)));
auto p_shared = make_shared<Person>(Person(40));
people_a.push_back(p_shared);
people_a.push_back(p_shared);
auto people_b = people_a;
std::cout << "people_a is......" << std::endl;
for (auto &p : people_a)
{
std::cout << "p is " << p << std::endl;
}
std::cout << "people_b is......" << std::endl;
for (auto &p : people_b)
{
std::cout << "p is " << p << std::endl;
}
for (auto &p : people_a)
{
if (p != nullptr)
{
p.reset();
p = nullptr;
}
}
std::cout<<people_a[0]<<std::endl;
assert(people_a[0]==nullptr);
std::cout<<people_a[0]->age<<std::endl;//Segmentation fault
for(auto &p:people_b)
{
std::cout<<p->age<<" ";
std::cout<<"p's reference num is "<<p.use_count()<<std::endl;
}
std::cout<<people_b.back().use_count()<<std::endl;
for (auto &p : people_b)
{
if (p != nullptr)
{
p.reset();
p = nullptr;
}
}
return 0;
}
/*
people_a is......
p is 0x55555556fec0
p is 0x55555556ff00
p is 0x55555556fee0
p is 0x55555556fee0
people_b is......
p is 0x55555556fec0
p is 0x55555556ff00
p is 0x55555556fee0
p is 0x55555556fee0
0
10 p's reference num is 1
20 p's reference num is 1
40 p's reference num is 3
40 p's reference num is 3 //前面有个auto p_shared
3
*/
7.5 引用并不会增加shared_ptr的计数
#include <iostream>
#include <memory>
using namespace std;
int main(int argc, char **argv)
{
auto p = make_shared<int>(10);
std::cout << "p's reference num is " << p.use_count() << std::endl;
auto &a = p;
std::cout << "p's reference num is " << p.use_count() << std::endl;
return 0;
}
/*
p's reference num is 1
p's reference num is 1
*/
7.6 赋值的时候注意
7.6.1 引用导致出错
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
int main(int argc, char **argv)
{
std::vector<shared_ptr<int>> nums_ptr;
nums_ptr.push_back(make_shared<int>(10));
auto &temp_ptr = nums_ptr.back();
std::cout << "temp_ptr use_count = " << temp_ptr.use_count() << std::endl;
std::cout << "nums_ptr[0] use_count=" << nums_ptr[0].use_count() << std::endl;
nums_ptr.push_back(temp_ptr);
nums_ptr.push_back(temp_ptr);
std::cout << "nums_ptr size is " << nums_ptr.size() << std::endl;
std::cout << "[0] use_count=" << nums_ptr[0].use_count() << std::endl;
std::cout << "[1] use_count=" << nums_ptr[1].use_count() << std::endl;
std::cout << "[2] use_count=" << nums_ptr[2].use_count() << std::endl;
return 0;
}
/*
temp_ptr use_count = 1
nums_ptr[0] use_count=1
nums_ptr size is 3
[0] use_count=2
[1] use_count=2
[2] use_count=1
Segmentation fault
*/
2
std::vector<shared_ptr<int>> nums_ptr;
nums_ptr.push_back(make_shared<int>(10));
auto &temp_ptr = nums_ptr.back();
std::cout << "temp_ptr use_count = " << temp_ptr.use_count() << std::endl;
std::cout << "nums_ptr[0] use_count=" << nums_ptr[0].use_count() << std::endl;
nums_ptr.push_back(temp_ptr);
nums_ptr.push_back(temp_ptr);
nums_ptr.push_back(temp_ptr);
std::cout << "nums_ptr size is " << nums_ptr.size() << std::endl;
std::cout << "[0] use_count=" << nums_ptr[0].use_count() << std::endl;
std::cout << "[1] use_count=" << nums_ptr[1].use_count() << std::endl;
std::cout << "[2] use_count=" << nums_ptr[2].use_count() << std::endl;
std::cout << "[3] use_count=" << nums_ptr[3].use_count() << std::endl;
return 0;
输出
temp_ptr use_count = 1
nums_ptr[0] use_count=1
nums_ptr size is 4
[0] use_count=2
[1] use_count=2
[2] use_count=2
[3] use_count=2
Segmentation fault
Segmentation fault发生在
上述会导致Segmentation fault析构的时候出现问题
_M_dispose()释放对象出现了问题
从引用计数来看一个temp引用的一个数组存的,先到引用计数为零,释放指向内存,后面释放就出问题了
emplace_back也是同样的问题
7.6.2 非引用
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
int main(int argc, char **argv)
{
std::vector<shared_ptr<int>> nums_ptr;
nums_ptr.push_back(make_shared<int>(10));
auto temp_ptr = nums_ptr.back();
nums_ptr.push_back(temp_ptr);
nums_ptr.push_back(temp_ptr);
std::cout << nums_ptr.size() << std::endl;
std::cout << nums_ptr[0].use_count() << std::endl;
temp_ptr.reset();
std::cout << nums_ptr[0].use_count() << std::endl;
return 0;
}
/*
3
4
3
*/
综合以上,对于指针指针慎重和引用一起使用(不要写入就行)
对于智能指针的判断:进行reset后进行置空,使用nullptr进行判断
不要用use_count对于那么多数据也不知道什么时候计数器清空
auto temp_ptr使用然后再消亡即可
7.6.3 attention
7.6.2和7.6.3都是对一个数组操作,取出某位数组然后存入
两个数组的时候
正常不会出现Segmentation fault
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
int main(int argc, char **argv)
{
std::vector<shared_ptr<int>> nums_ptr;
nums_ptr.push_back(make_shared<int>(10));
vector<shared_ptr<int>> new_nums;
auto &temp_ptr = nums_ptr.back();
std::cout << "temp_ptr count =" << temp_ptr.use_count() << std::endl;
std::cout << "nums_ptr[0] count =" << nums_ptr[0].use_count() << std::endl;
std::cout << "=============================" << std::endl;
new_nums.push_back(temp_ptr);
new_nums.push_back(temp_ptr);
std::cout << "temp_ptr count =" << temp_ptr.use_count() << std::endl;
std::cout << "nums_ptr[0] count =" << nums_ptr[0].use_count() << std::endl;
std::cout << "new_nums[0] count = " << new_nums[0].use_count() << std::endl;
std::cout << "new_nums[1] count = " << new_nums[1].use_count() << std::endl;
return 0;
}
输出结果
temp_ptr count =1
nums_ptr[0] count =1
=============================
temp_ptr count =3
nums_ptr[0] count =3
new_nums[0] count = 3
new_nums[1] count = 3
显然对智能指针的应用并不增加智能指针的引用计数
7.6.3 注意
因为引用不可更改对象,
问题出现在对存放智能指针的同一个数组 对里面数组的进行取出(引用),再放入同一数组。
引用计数并没有按照预想的增加
你对
for(auto& p:people){}
这种对单个对象(读)操作还好(基本上都这么用),但是放进了数组中
放进一个位置,只绑定了一个对象,不出错(难保以后不出错)
避免用同一个数组引用智能指针存取
7.7 直接删除数组的某个元素
#include <iostream>
#include <deque>
#include <memory>
#include <vector>
using namespace std;
class Person
{
public:
Person(int _age) : age(_age)
{
}
int age;
};
int main(int argc, char **argv)
{
std::vector<shared_ptr<Person>> people_a;
people_a.push_back(make_shared<Person>(Person(10)));
people_a.push_back(make_shared<Person>(Person(20)));
auto p_shared = make_shared<Person>(Person(40));
people_a.push_back(p_shared);
people_a.push_back(p_shared);
for (int i = 0; i < people_a.size(); ++i)
{
std::cout << people_a[i].use_count() << std::endl;
}
std::cout << "people_a's size is " << people_a.size() << std::endl;
people_a.pop_back();
for (int i = 0; i < people_a.size(); ++i)
{
std::cout << people_a[i].use_count() << std::endl;
}
std::cout << "people_a's size is " << people_a.size() << std::endl;
return 0;
}
/*
1
1
3
3
people_a's size is 4
1
1
2
people_a's size is 3
*/
/*
有多一个的原因是前面 auto p_shared = make_shared<Person>(Person(40));有一个引用计数
*/
引用计数减一 数组的size()减一:这样销毁一个指针,引用计数减一
7.8 erase
#include <iostream>
#include <deque>
#include <memory>
#include <vector>
using namespace std;
class Person
{
public:
Person(int _age) : age(_age)
{
}
int age;
};
int main(int argc, char **argv)
{
std::vector<shared_ptr<Person>> people_a;
people_a.push_back(make_shared<Person>(Person(10)));
people_a.push_back(make_shared<Person>(Person(20)));
auto p_shared = make_shared<Person>(Person(40));
people_a.push_back(p_shared);
people_a.push_back(p_shared);
for (int i = 0; i < people_a.size(); ++i)
{
std::cout << people_a[i].use_count() << std::endl;
}
std::cout << "people_a's size is " << people_a.size() << std::endl;
//people_a.pop_back();//(1)
// auto it = people_a.end();(2)
// it--;
// people_a.erase(it);
// auto it = people_a.begin();//(3)
// it+=2;
// people_a.erase(it);
for (int i = 0; i < people_a.size(); ++i)
{
std::cout << people_a[i].use_count() << std::endl;
}
std::cout << "people_a's size is " << people_a.size() << std::endl;
return 0;
}
/*(1)
1
1
3
3
people_a's size is 4
1
1
2
people_a's size is 3
*/
/*
有多一个的原因是前面 auto p_shared = make_shared<Person>(Person(40));有一个引用计数
*/
/*(2)
1
1
3
3
people_a's size is 4
1
1
2
people_a's size is 3
*/
/*(3)
1
1
3
3
people_a's size is 4
1
1
2
people_a's size is 3
*/
八、智能指针管理类
有的类会去删除拷贝构造函数,然后让智能指针管理这个类,这样这个类的拷贝就变成智能指针的浅拷贝
RAII与智能指针
1:53:48
8.1 示例说明
mention
// Person *p1 = new Person(10, 120);
// Person *p2(p1);
// Person *p3 = p1;
上述直接进行的是指针的赋值,不会下面的共享内存拷贝
#include <iostream>
#include <memory>
using namespace std;
class Person
{
public:
Person(int _age, int _height) : age(_age)
{
height = make_shared<int>(_height);
}
int age;
shared_ptr<int> height;
};
int main()
{
// Person *p1 = new Person(10, 120);
// Person *p2(p1);
// Person *p3 = p1;
// std::cout<<p1->height<<std::endl;//0x55555556cee0
// std::cout<<p2->height<<std::endl;//0x55555556cee0
// std::cout<<p3->height<<std::endl;//0x55555556cee0
// std::cout<<p3->height.use_count()<<std::endl;//1
// p3->height.reset();
// std::cout<<*(p2->height);//Segmentation fault
// std::cout<<"----------------"<<std::endl;
Person p4(10, 120);
Person p5(p4);
Person p6 = p4;
std::cout << p4.height << std::endl;//0x55555556cec0
std::cout << p5.height << std::endl;//0x55555556cec0
std::cout << p6.height << std::endl;//0x55555556cec0
std::cout << p4.height.use_count() << std::endl;//3
p4.height.reset();
std::cout << *(p5.height)<<std::endl;//120
std::cout << p4.height.use_count() << std::endl;//0
std::cout << p5.height.use_count() << std::endl;//2
std::cout << "----------------" << std::endl;
return 0;
}
8.2 含有vector<ptr>的拷贝
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
class Person
{
public:
Person(int _age, int _height) : age(_age)
{
height = make_shared<int>(_height);
measure_buf.push_back(make_shared<int>(_height));
}
int age;
vector<shared_ptr<int>> measure_buf;
shared_ptr<int> height;
};
int main()
{
Person p1(10, 120);
Person p2(p1);
Person p3(p1);
std::cout << p1.measure_buf[0] << std::endl;
std::cout << p2.measure_buf[0] << std::endl;
std::cout << p3.measure_buf[0] << std::endl;
std::cout << p1.measure_buf[0].use_count() << std::endl;
std::cout << "==================" << std::endl;
vector<shared_ptr<int>> a_buf;
a_buf.push_back(make_shared<int>(100));
auto b_buf = a_buf;
std::cout << "a_buf和b_buf的地址" << std::endl;//两者的地址是不一样的,可以说明是深拷贝
std::cout << &a_buf[0] << std::endl;//0x555555570380
std::cout << &b_buf[0] << std::endl;//0x5555555703a0
std::cout << "里面数据" << std::endl;
std::cout << a_buf[0] << std::endl;//0x555555570370
std::cout << b_buf[0] << std::endl;//0x555555570370
std::cout << a_buf[0].use_count() << std::endl;//2
std::cout << b_buf[0].use_count() << std::endl;//2
std::cout << "after reset" << std::endl;
a_buf[0].reset();
std::cout << a_buf[0] << std::endl;//0->nullptr
std::cout << b_buf[0] << std::endl;//0x555555570370
std::cout << a_buf[0].use_count() << std::endl;//0
std::cout << b_buf[0].use_count() << std::endl;//1
std::cout << *(b_buf[0]) << std::endl;//100
return 0;
}
从a_buf=b_buf得出的首地址不一致可以看出新开辟了内存
vector内数据使用结构体的话是深拷贝,vector内的数据会拷贝一份保存,vector内数据不会丢失。如果vector内数据是指针的话是进行浅拷贝,数据超出作用域后会自动析构,vector内所指向的数据会被更改和丢失,所以vector如果作为全局变量,不应该使用指针。
其实就是按照Values拷贝,非指针新开辟内存,指针直接赋值
从a_buf=b_buf得出的首地址不一致是vector的深拷贝,里面的是按值拷贝
九、右值引用
C++ Primer 5th 中文版 Page 471
为了支持移动操作,新标准引入了一种新的引用类型----右值引用
右值引用—>必须绑定到右值的引用:通过&&来获得右值引用
右值引用的性质–>只能绑定到将要销毁的对象上。因此可以自由地将一个右值引用的资源“移动”到另一个对象上
一般而言,一个左值表达式表示的是一个对象的身份,而右值表达式表示的是对象的值
C++ Primer 5th 中文版 Page 121:当一个对象被用作右值的时候,用的是对象的值(内容),当对象被用作左值的时候,用的是对象的身份(在内存中的位置)
int i=42;
int &r = i;//正确,左值引用
int &&rr = i;//错误,不能将一个右值引用绑定到一个左值上
int &r2 = i*42;//错误i*42是一个右值
const int &r3 = i*42;//正确,可以将一个const的引用绑定到一个右值上
int &&rr2 = i*42;//正确,将rr2绑定到乘法结果上
9.1 左值持久,右值短暂
由于右值引用只能绑定到临时对象上,所以
所引用的对象将要被销毁
该对象没有其他用户
所以使用右值引用的代码可以自由的接管所引用的对象的资源
右值引用指向将要被销毁的对象。因此,我们可以从绑定到右值引用的对象“窃取”状态
十、虚函数、纯虚函数、虚函数指针、虚函数表
10.1 虚函数、纯虚函数
子类继承父类并重写父类的虚函数实现多态
定义纯虚函数的类是抽象类,不可以被实例化,如果子类继承该类但没有重写纯虚函数,子类将也是抽象类不可被实例化。
#include <iostream>
using namespace std;
class Base
{
public:
virtual void speak()
{
std::cout << "base" << std::endl;
}
};
class Base2
{
public:
virtual void speak() = 0;
};
int main()
{
Base base; //只有当虚函数变成纯虚函数时候,才可以变成抽象类,不可实例化对象
Base2 base;//object of abstract class type "Base2" is not allowed 纯虚函数不可被实例化
base.speak();
return 0;
}
10.1.2 需全部重写否则仍然为抽象类
#include <iostream>
using namespace std;
class Base
{
public:
virtual void speak()
{
std::cout << "base speak" << std::endl;
}
virtual void cry()
{
std::cout << "base cry" << std::endl;
}
};
class Base2
{
public:
virtual void speak() = 0;
virtual void cry() = 0;
};
class Son2 : public Base2
{
public:
virtual void speak()
{
std::cout << "Son2 speak" << std::endl;
}
virtual void cry()
{
std::cout << "Son2 cry" << std::endl;
}
};
void a()
{
}
int main()
{
Base base1; //只有当虚函数变成纯虚函数时候,变成抽象类,不可实例化对象
// Base2 base2;//object of abstract class type "Base2" is not allowed 纯虚函数不可被实例化
Son2 son2;
base1.speak();
// printf("%p\n",&a);
std::cout << &a << std::endl;
return 0;
}
10.2 C++对象模型、虚函数指针、虚函数表
深度探索C++对象模型 Page 37:
Stroustrup当初设计(⽬前仍占有优势)的C++对象模型是从简单对象模型派⽣⽽来的,并对内存空间和存取时间做了优化。在此模型中,Nonstatic data members被配置于每⼀个class object之内,static data members则被存放在个别的class object之外。Static和nonstatic function members也被放在个别的class object之外。Virtual functions则以两个步骤⽀持之:
注:成员和成员函数存储是不同的
1.每⼀个 class产⽣出⼀堆指向 virtual functions 的指针,放在表格之中。这个表格被称为 virtual table(vtbl )。
2 .每⼀个 class object被安插⼀个指针,指向相关的 virtual table。通常这个指针被称为 vptr 。vptr 的设定(setting)和重置(resetting)都由每⼀个 class的 constructor、destructor和 copy assignment运算符⾃动完成(我将在第5章讨论这个问题)。每⼀个 class所关联的 type_info object(⽤以⽀持 runtime type identification,RTTI)也经由 virtual table被指出来,通常放在表格的第⼀个 slot。
图片来自黑马
取地址
十一、移动构造、移动赋值
参考小彭老师
三五法则:
如果一个类定义了解构函数,那么必须同时定义或删除拷贝构造函数和拷贝赋值函数,否则出错。
如果一个类定义了拷贝构造函数,那么必须同时定义或删除拷贝赋值函数,否则出错,删除可导致低效。
如果一个类定义了移动构造函数,那么必须同时定义或删除移动赋值函数,否则出错,删除可导致低效。
如果一个类定义了拷贝构造函数或拷贝赋值函数,那么必须最好同时定义移动构造函数或移动赋值函数,否则低效。
11.1 移动
有时需要把一个对象v2移动到v1上,不需要涉及实际数据的拷贝
时间复杂度:移动是O(1),拷贝是O(n),
使用std::move()实现移动
v2被移动到v1后,原来v2会被清空因此仅当v2再也用不到的时候采用移动
11.1.1 交换
std::swap(v1,v2)
std::swap()定义在 /usr/include/c++/9/bits---->std::move底层也应该是利用std::move()实现的
可以参考一下c++ std::swap()和STL提供的swap()成员函数的不同
即vector<T>::swap()
(a.swap(b))只交换了指向的地址
11.1.2 触发移动的情况
//这些情况下编译器会调用移动:
return v2 // v2 作返回值
v1 = std::vector<int>(200) // 就地构造的 v2
v1 = std::move(v2) // 显式地移动
//这些情况下编译器会调用拷贝:
return std::as_const(v2) // 显式地拷贝
v1 = v2 // 默认拷贝
//注意,以下语句没有任何作用:
std::move(v2) // 不会清空 v2,需要清空可以用 v2 = {} 或 v2.clear()
std::as_const(v2) // 不会拷贝 v2,需要拷贝可以用 { auto _ = v2; }
//这两个函数只是负责转换类型,实际产生移动/拷贝效果的是在类的构造/赋值函数里。
std::move(t) 相当于 (T &&)t
std::as_const(t) 相当于 (T const &)t
11.1.2 移动构造:缺省实现
同样,如果对降低时间复杂度不感兴趣:
移动构造≈拷贝构造+他解构(other)+他默认构造(this)
移动赋值≈拷贝赋值+他解构(other)+他默认构造(this)
只要不定义移动构造和移动赋值,编译器会自动这样做。虽然低效,但至少可以保证不出错。
若自定义了移动构造,对提高性能不感兴趣:
移动赋值≈解构+移动构造(使用了std::move)
11.1.3 小技巧:如果有移动赋值函数,可以删除拷贝赋值函数
其实:如果你的类已经实现了移动赋值函数,那么为了省力你可以删除拷贝赋值函数。
这样当用户调用: v2 = v1时,
因为拷贝赋值被删除,编译器会尝试: v2 = List(v1)
从而先调用拷贝构造函数,然后因为 List(v1)
相当于就地构造的对象,从而变成了移动语义,从而进一步调用移动赋值函数
十二、智能指针
std::shared_ptr 共享 引用计数器 浅拷贝
std::unique_ptr 独享 应用计数器 禁止拷贝
std::weak_ptr 弱应用(与std::shared_ptr使用不会增加共享指针的计数器) 浅拷贝
12.1 std::shared_ptr
//创建方式1
std::shared_ptr<int> p = std::make_shared<int>(42);
//创建方式2 如果不初始化一个智能指针,它会被初始化一个空指针
std::shared_ptr<int> p1;
std::shared_ptr<int> p2(new int(1024));
12.1.1 不要使用get初始化另一个指针或为智能指针赋值
来自C++ Primer 5th 中文版 Page 414 代码结果有点不符合预期?
std::shared_ptr<int> p(new int(42));//引用计数为1
int *q = p.get();//正确:但是不要让q管理的内存被释放
{
//新程序块
//未定义:两个独立的shared_ptr指向相同的内存
std::shared_ptr<int>(q);
}//程序块结束,q被销毁,指向的内存被释放
int foo = *p;//未定义;p指向的内存已经被释放
见12.1.1.1 实验一的结果直接正常打印输出了
上述更正一下见下面分析
12.1.1.1 实验一
#include <iostream>
#include <memory>
using namespace std;
int main()
{
std::shared_ptr<int> p(new int(42)); //引用计数为1
int *q = p.get();//正确:但是不要让q管理的内存被释放
{
//新程序块
std::shared_ptr<int>(q); //就地,右值即将消亡不会运行后,p的引用计数仍然有
}
int foo = *p;
std::cout<<"foo="<<foo<<std::endl;//42
return 0;
}
正常输出
12.1.1.2 实验二
#include <iostream>
#include <memory>
using namespace std;
int main()
{
std::shared_ptr<int> p(new int(42));
int *q = p.get();
{
auto other = std::shared_ptr<int>(q);
}
int foo = *p;
std::cout<<"foo="<<foo<<std::endl;
return 0;
}
foo=0
free(): double free detected in tcache 2
智能指针other离开作用域 引用计数变为零销毁指向的内存,智能指针指向的对象消失,输出了0
智能指针析构导致double free
指针q在main()作用域下
debug模式发生在 shared_ptr_base.h的第376-377行:智能指针double free了
_M_dispose() noexcept
{ delete _M_ptr; }
12.1.1.3 实验3
#include <iostream>
#include <memory>
using namespace std;
int main()
{
std::shared_ptr<int> p(new int(42));
{
int *q = p.get();
std::shared_ptr<int>(q);
}
int foo = *p;
std::cout << "foo=" << foo << std::endl;
return 0;
}
/*
编译不通过
error: conflicting declaration ‘std::shared_ptr<int> q’
10 | std::shared_ptr<int>(q);
note: previous declaration as ‘int* q’
9 | int *q = p.get();
*/
十三、多线程死锁与线程安全
由于同时执行的两个线程,他们中发生的指令不一定是同步的,因此有可能出现这种情况:
t1 执行 mtx1.lock()。
t2 执行 mtx2.lock()。
t1 执行 mtx2.lock():失败,陷入等待
t2 执行 mtx1.lock():失败,陷入等待
双方都在等着对方释放锁,但是因为等待而无法释放锁,从而要无限制等下去。
这种现象称为死锁(dead-lock)。
两个线程持有锁,都在等待对方的锁释放
左目图像和右目图像传入的话用两个锁要考虑锁的顺序
与此同时,同一个线程重复调用lock()也会造成死锁
13.1 一个简单的单生产者单消费者无锁队列
#include <iostream>
#include <queue>
#include <thread>
//#include <mutex>
using namespace std;
int num = 0;
//std::mutex mtx;
queue<int> qu;
class Producer
{
public:
void write(std::queue<int> &qu)
{
while (1)
{
//std::unique_lock<mutex> ul(mtx);
qu.push(num);
num++;
if(num>10000) //不加这一行会有double free or corruption (!prev)
{
num=0;
}
//std::cout<<"qu size is "<<qu.size()<<std::endl;//不加肯会出现 Segmentation fault (core dumped)
//加了上一行 qu size is 6519121 还没出错(很明显时间比上面的时间要长的多)
//分析qu size 一直在增长,但是加了一句打印显然要时间拖慢了Segmentation fault (core dumped)应该是qu所占内存越来越大把内存干崩了
//std::cout<<"qu size is "<<qu.size()<<std::endl;
printf("qu size is %d",(int)qu.size());
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
};
/*
std::this_thread::sleep_for(std::chrono::milliseconds(100));
交互会有问题
qu size is 0
241
qu size is 0
242
qu size is 0
243
qu size is 0
qu size is
244
1
qu size is
245
1
qu size is
246
1
247
qu size is 0
qu size is
248
1
qu size is 249
1
250qu size is
1
qu size is
251
1
252
qu size is 0
qu size is 253
1
qu size is 1
254
qu size is
255
1
^C
https://blog.csdn.net/ctthuangcheng/article/details/9177885
(1)printf:在对标准输出作任何处理前先加锁。
(2)std::cout:在实际向标准输出打印时方才加锁。
换成printf
165
qu size is 1
166
qu size is 1
167
qu size is 1
168
qu size is 1qu size is 1
169
170
qu size is 1
171
qu size is 1
172
qu size is 1
问题小很多
*/
class Consumer
{
public:
void read(std::queue<int> &qu)
{
while (1)
{
if (!qu.empty())
{
//std::unique_lock<mutex> ul(mtx);
int temp = qu.front();
printf("\n%d\n",temp);
//std::cout <<std::endl<<temp<<std::endl;
qu.pop();
//return temp;//退出去读写的循环就被破坏了 如果想获取数据就用引用传值
}
}
}
};
int main(int argc, char **argv)
{
Producer producer;
Consumer consumer;
std::thread t1(&Producer::write, &producer, std::ref(qu));
std::thread t2(&Consumer::read, &consumer, std::ref(qu));
t1.join();
t2.join();
return 0;
}
13.2 std::thread构造函数传参和std::ref
13.2.1 代码
#include <iostream>
#include <queue>
#include <thread>
class ThreadPool
{
std::vector<std::thread> m_pool;
public:
void push_back(std::thread thr)
{
m_pool.push_back(std::move(thr));
}
~ThreadPool()
{
for(auto &t:m_pool)
{
t.join();
}
}
};
using namespace std;
int num = 0;
queue<int> qu;
class Producer
{
public:
void write(std::queue<int> &qu)
{
while (1)
{
qu.push(num);
num++;
if(num>10000)
{
num=0;
}
printf("qu size is %d",(int)qu.size());
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
};
class Consumer
{
public:
void read(std::queue<int> &qu, int &num_out )
{
while (1)
{
if (!qu.empty())
{
int temp = qu.front();
num_out = temp;
printf("\n%d\n",temp);
qu.pop();
}
}
}
};
ThreadPool tpool;
int main(int argc, char **argv)
{
Producer producer;
Consumer consumer;
int a=0;
std::thread t1(&Producer::write, &producer, std::ref(qu));
std::thread t2(&Consumer::read, &consumer, std::ref(qu),std::ref(a));
tpool.push_back(std::move(t1));
tpool.push_back(std::move(t2));
std::this_thread::sleep_for(std::chrono::milliseconds(422));
std::cout<<"a ============== "<<a<<std::endl;//a ============== 4
return 0;
}
上述代码是类的成员函数地址,调用的对象,参数传递列表(引用是std::ref,值传递就是std::thread t2(f1, n + 1); ),对上就行
13.2.2 std::ref
用来构建一个reference_wrapper对象并返回,该对象拥有传入的elem变量的引用。如果参数本身是一个reference_wrapper类型的对象,则创建该对象的一个副本,并返回。
thread的方法传递引用的时候,必须用ref来进行引用传递,否则就是浅拷贝。
std::ref只是尝试模拟引用传递,并不能真正变成引用,在非模板情况下,std::ref根本没法实现引用传递,只有模板自动推导类型或类型隐式转换时,ref能用包装类型reference_wrapper来代替原本会被识别的值类型,而reference_wrapper能隐式转换为被引用的值的引用类型。
std::ref和std::reference_wrapper
std::ref详解
13.3.3 std::thread构造
std::thread 构造方法
(1)默认构造函数 thread() noexcept;
(2)初始化构造函数 template <class Fn, class… Args>
(3)拷贝构造函数 thread (const thread&) = delete;
(4)move构造函数 thread (thread&& x) noexcept;
(1)默认构造函数:创建一个空thread对象,该对象非joinable
(2)初始化构造函数:创建一个thread对象,该对象会调用Fn函数,Fn函数的参数由args指定,该对象是joinable的
(3)拷贝构造函数:被禁用,意味着thread对象不可拷贝构造
(4)move构造函数:移动构造,执行成功之后x失效,即x的执行信息被移动到新产生的thread对象,该对象非joinable
值传递使用值传递,引用使用std::ref
调用对象的成员函数就是 成员函数地址,调用的对象地址,参数列表
13.3 无锁队列的实现学习(TODO)
13.4 数据结构安全
STL容器不是线程安全的
13.4.1 mutable
vector<>::size()是const的mutux::lock()不是const的利用这去保护vector的访问(size时会出错)
逻辑上是const而部分成员非const:采用mutable
mutable std::mutex m_mtx;
/**
size() 在逻辑上仍是 const 的。因此,为了让 this 为 const 时仅仅给 m_mtx 开后门,
可以用 mutable 关键字修饰他,从而所有成员里只有他不是 const 的。
*/