文章目录
- 1. 智能指针(shared_ptr、unique_ptr、weak_ptr)
-
- 1.1 智能指针用来解决什么问题?
- 1.2 shared_ptr
-
- 1.2.1 shared_ptr内存模型
- 1.2.2 shared_ptr的基本用法
- 1.2.3 使用shared_ptr需要注意的问题
- 1.3 unique_ptr
- 1.4 weak_ptr
-
- 1.4.1 weak_ptr的基本用法
- 1.5 智能指针的安全问题
- 2. 左引用和右引用
-
-
- 2.1 移动结构函数
- 2.2 移动(move)语义
- 2.3 forward 完美转发
-
- 3. lambda 表达式
-
- 3.1 匿名函数的基本用法
1. 智能指针(shared_ptr、unique_ptr、weak_ptr)
1.1 智能指针用来解决什么问题?
?智能指针主要解决以下问题: ???1. 内存泄漏:手动释放堆上申请的内存(new/malloc),可自动释放智能指针内存。 ???2.共享所有权指针的传播和释放:例如,同一对象的多线程分析。
?智能指针的区别: ???shared_ptr共享对象的所有权,性能越差; ???unique_ptr由于没有引用计数,独享对象的所有权性能较好; ???weak_ptr配合shared_ptr,解决循环引用问题。
1.2 shared_ptr
1.2.1 shared_ptr内存模型
??std::shared_ptr使用引用计数,每个shared_ptr复制指向相同的内存。最后一个shared_ptr内存只有在分析时才会释放。 ??std::shared_ptr内部有两个指针,一个指向对象,另一个指向控制块(control block),控制块包含一个引用计数(reference count), 一个弱计数(weak count)还有其他数据。 ??,当前这个堆上对象被多少对象引用了,简单来说就是引用计数。
1.2.2 shared_ptr的基本用法
?
??1. 构造函数,std::shared_ptr辅助函数和reset初始化方法shared_ptr; ??2. 优先使用make_shared因为他更有效率,所以建立指针指针。 ??3. 给智能指针赋值不能说原指针。 ??4. 当智能指针值时,调用reset()会导致计数减少1.
//shared_ptr初始化:构造函数,make_shared、reset()方法 int main(){
shared_ptr<int> p1(new int(1)); /// shared_ptr<int> p2 = p1; //复制构造函数 shared_ptr<int> p3; p3.reset(new int(100)); //reset()方法 shared_ptr<int> p4 = make_shared<int>(100); ///不能给智能指针赋值裸指针 //shraed_ptr<int> p5 = new int(5); //错误的
if
(p1
)
{
cout
<<
"p1 use_count(): "
<< p1
.
use_count
(
)
<< endl
;
// 2 p2
.
reset
(
)
;
//引用计数-1 cout
<<
"p1 use_count(): "
<< p1
.
use_count
(
)
<< endl
;
// 1
}
return
0
;
}
//使用get()方法获取裸指针p,delete p会导致裸指针被释放两次
{
shared_ptr<int> ptr = make_shared<int>(100);
int *p = ptr.get();
//delete p; //double free or corruption (out)
}
int main(){
//如果使用shared_ptr管理非new对象或者是没有析构函数的类时,应当为其传递合适的删除器
shared_ptr<int> p1(new int(10), DeleteIntPtr);
//删除器可以是一个lambda表达式
shared_ptr<int> p2(new int(20), [](int *p)->void{
cout << "call lambda delete p" << endl;
delete p;
});
//当我们使用shared_ptr管理动态数组时,需要指定删除器,因为shared_ptr的默认删除器不支持对数组对象。
shared_ptr<int> p3(new int[10], [](int *p){
cout << "call lambda delete p[]" << endl;
delete []p;
});
}
1.2.3 使用shared_ptr要注意的问题
- ,会出现同一个资源被多次释放的错误。
- ,不同编译器函数的参数计算顺序是不是一样的,可能会存在已经new创建的对象资源,由于异常shared_ptr智能指针还来不及创建,出现内存泄漏。
- ,this指针本身就是一个裸指针,函数返回shared_ptr的this指针,为其他的shared_ptr指针初始化,其本质相当于使用同一个原始指针初始化多个shared_ptr,会导致资源被释放多次。
- ,循环引用导致智能指针的引用计数为2,但是离开作用域之后智能指针的引用计数减为1,并没有减为0,导致两个指针都不会被析构,造成内存泄漏。 。
class A;
class B;
class A {
public:
std::shared_ptr<B> bptr;
~A() {
cout << "A is deleted" << endl;
}
};
class B {
public:
std::shared_ptr<A> aptr;
~B() {
cout << "B is deleted" << endl; // 析构函数后,才去释放成员变量
}
};
void cycle_shared_ptr(){
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
pa->bptr = pb;
pb->aptr = pa;
}
int main(){
//使用一个原始指初始化多个shared_ptr,会导致对象被多次释放
int *ptr = new int(10);
shared_ptr<int> p1(ptr);
//shared_ptr<int> p2(ptr); //free(): double free detected in tcache 2
/* 不要在函数实参中创建shared_ptr,因为不同编译器对函数参数的计算顺序约定是不一样的。 可能是从右到左,也可能是从左到右。 所以,有可能是先new int,然后调用g(),如果恰好g()发生异常,而shared_ptr还没有创建,则会导致内存泄漏。 */
//function(shared_ptr<int>(new int), g()); //有缺陷
//循环引用,没有调用析构函数
cycle_shared_ptr();
}
1.3 unique_ptr
- unique_ptr 是一个独占型的智能指针,不能将一个unique_ptr对象赋值给另一个unique_ptr对象。
- unique_ptr 可以指向一个数组对象。
- unique_pt 需要指定删除器的类型。
- 如果希望只有一个智能指针管理资源或者管理数组就用unique_ptr,如果希望多个智能指针管理同一个资源就用shared_ptr。
int main()
{
//unique_ptr是一个独享型智能指针,不能将一个unique_ptr赋值给另外一个unique_ptr
auto p1 = make_unique<int>(10);
//unique_ptr<int> p2 = p1; //错误
//使用std::move强unique_ptr对象转移
unique_ptr<int> p3 = move(p1); //正确
if(!p1){
cout << "p1 is null" << endl;
}else{
cout << "p1 is not null" << endl;
}
//unique_ptr可以指向一个数组
unique_ptr<int []> ptr1(new int[10]); //正确
shared_ptr<int []> ptr2(new int[10]); //错误
//unique_ptr需要指定删除器的类型
std::unique_ptr<int, void(*)(int*)> ptr3(new int(1), [](int *p){
cout << "unique_ptr call lambda delete p" << endl;
delete p;
});
std::shared_ptr<int> ptr4(new int(1), [](int *p){
cout << "shared_ptr call lambda delete p" << endl;
delete p;
});
return 0;
}
1.4 weak_ptr
weak_ptr 是一种, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的shared_ptr, weak_ptr只是提供了对管理对象的一个访问手段。它只能从一个shared_ptr 或 另一个weak_ptr对象构造。 weak_ptr没有重载操作符*和->,因为它不共享指针,不能操作资源,主要是为了通过shared_ptr获得资源的监测权,它的构造不会增加引用计数,它的析构也不会减少引用计数,纯粹只是作为一个旁观者来。weak_ptr还可以返回this指针和解决循环引用的问题。
int main()
{
shared_ptr<int> p = make_shared<int>(10);
//weak_ptr<int> p1(new int(10)); //错误的,只能从一个shared_ptr 或 weak_ptr对象构造
weak_ptr<int> p2(p);
weak_ptr<int> p3(p2);
//p use_count(): 1
cout << "p use_count(): " << p3.use_count() << endl; //weak_ptr不会改变引用计数器
return 0;
}
1.4.1 weak_ptr的基本用法
- 通过use_count()方法获取当前观察对象的引用计数
- 通过expired()方法判断观察者资源是否已经释放
- 通过lock方法获取监视的shared_ptr
#include <iostream>
#include <memory>
using namespace std;
class A;
class B;
class A {
public:
std::weak_ptr<B> bptr; // 修改为weak_ptr
int *val;
A() {
val = new int(1);
}
~A() {
cout << "A is deleted" << endl;
delete val;
}
};
class B {
public:
std::shared_ptr<A> aptr;
~B() {
cout << "B is deleted" << endl;
}
};
//weak_ptr解决循环引用的问题
void test(){
std::shared_ptr<A> aptr(new A);
std::shared_ptr<B> bptr(new B);
aptr->bptr = bptr;
bptr->aptr = aptr;
}
int main(){
std::weak_ptr<A> wp;
std::shared_ptr<A> ap(new A);
std::shared_ptr<A> ap2 = ap;
wp = ap;
cout<< "wp.use_count() " << wp.use_count() << " , wp.expired(): " << wp.expired() << endl;
if(!wp.expired()) {
// wp不能直接操作对象的成员、方法
//cout << "wp->val" << *(wp->val) << endl; //错误,error: base operand of ‘->’ has non-pointer type ‘std::weak_ptr<A>’
std::shared_ptr<A> ptr = wp.lock(); // 需要先lock获取std::shared_ptr<A>
*(ptr->val) = 20; // 是不是有问题了?
cout << "ptr->val " << *(ptr->val) << endl;
}
test();
}
shared_ptr章节中提到不能直接将this指针返回shared_ptr,需要通过派生std::enable_shared_from_this类,并通过其方法shared_from_this来返回指针,原因是std::enable_shared_from_this类中有一个weak_ptr,这个weak_ptr用来观察this智能指针,调用shared_from_this()方法是,会调用内部这个weak_ptr的lock()方法,将所观察的shared_ptr智能指针返回。
#include <iostream>
#incldue <merroy>
using namespace std;
class A public atd::enable_shared_from_this{
~A(){
cout<< "Desstructor A" <<endl;
}
shared_ptr<A> GetSelf(){
return shared_from_this();
}
};
int main(){
shared_ptr<A> ptr1 = make_shared<A>();
shared_ptr<A> ptr2 = ptr1.GetSelf();
}
1.5 智能指针的安全性问题
情况1:多线程代码操作的是同一个shared_ptr的对象,此时是不安全的。 情况2:多线程代码操作的不是同一个shared_ptr的对象 。
为什么多线程读写 shared_ptr 要加锁?陈硕的博客-CSDN博客多线程读写
2. 左值引用与右值引用
- 从性能上讲,左右值引用没有区别,传参使用左右值引用都可以避免拷贝。
- 右值引用可以直接指向右值,也可以通过std::move指向左值;而左值引用只能指向左值(const左值引用也能指向右值)。
- 作为函数形参时,右值引用更灵活。虽然const左值引用也可以做到左右值都接受,但它无法修改,有一定局限性。
2.1 移动构造函数
#include <iostream>
using namespace std;
class A{
public:
A():m_ptr(new int(10)){
cout << "A constructor" << endl;
};
A(const A& a):m_ptr(new int(*a.m_ptr)){
cout << "A copy constructor" << endl;
}
A(A&& a):m_ptr(a.m_ptr){
a.m_ptr = nullptr;
cout << "move constructor" << endl;
}
~A(){
cout << "Destruvtor A, " << m_ptr<< endl;
if(m_ptr) delete m_ptr;
m_ptr = nullptr;
}
private:
int *m_ptr;
};
A Get(bool flag){
A a;
A b;
cout << "================" << endl;
if(flag)
return a;
else
return b;
}
int main()
{
{
A a = Get(false);
}
cout << "main finish" << endl;
return 0;
}
执行结果:调用默认的浅拷贝构造函数
对于含有堆内存的类,我们需要提供深拷贝的拷贝构造函数,如果使用默认构造函数,会导致堆内存的重复删除.
执行结果:调用深拷贝拷贝构造函数
深拷贝构造函数虽然避免了对内存重复删除的问题;但是在Get函数中会返回一个临时变量,然后通过这个临时变量拷贝构造了一个新的对象b,临时变量在拷贝构造完成后就销毁了,如果对内存很大,那么,这个拷贝构造的代价会很大,带来了额外的性能损耗。
执行结果:调用移动构造函数 移动构造函数可以将资源通过浅拷贝的方式从一个对象转移到另一个对象,避免了临时对象的创建。移动构造函数的参数是一个右值引用类型&&,用来支持移动语义的。
2.2 移动(move)语义
move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转义,没有内存拷贝。要move语义起作用,核心在于需要对应类型的构造函数支持。
#include <iostream>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <string.h>
using namespace std;
class MyString {
private:
char* m_data;
size_t m_len;
void copy_data(const char *s) {
m_data = new char[m_len+1];
memcpy(m_data, s, m_len);
m_data[m_len] = '\0';
}
public:
MyString() {
m_data = NULL;
m_len = 0;
}
MyString(const char* p) {
m_len = strlen (p);
copy_data(p);
}
MyString(const MyString& str) {
m_len = str.m_len;
copy_data(str.m_data);
std::cout << "Copy Constructor is called! source: " << str.m_data << std::endl;
}
MyString& operator=(const MyString& str) {
if (this != &str) {
m_len = str.m_len;
copy_data(str.m_data);
}
std::cout << "Copy Assignment is called! source: " << str.m_data << std::endl;
return *this;
}
// 用c++11的右值引用来定义这两个函数
MyString(MyString&& str) {
std::cout << "Move Constructor is called! source: " << str.m_data << std::endl;
m_len = str.m_len;
m_data = str.m_data; //避免了不必要的拷贝
str.m_len = 0;
str.m_data = NULL;
}
MyString& operator=(MyString&& str) {
std::cout << "Move Assignment is called! source: " << str.m_data << std::endl;
if (this != &str) {
m_len = str.m_len;
m_data = str.m_data; //避免了不必要的拷贝
str.m_len = 0;
str.m_data = NULL;
}
return *this;
}
virtual ~MyString() {
if (m_data) free(m_data);
}
};
int main()
{
MyString a;
a = MyString("Hello"); // move
MyString b = a; // copy
MyString c = std::move(a); // move, 将左值转为右值
return 0;
}
2.3 forward 完美转发
forward 完美转发实现了,即若是左值,则传递之后仍然是左值,若是右值,则传递之后仍然是右值。
#include <iostream> using namespace std; template <class T> void Print(T &t){ cout <<