半小时掌握C++11新特性之智能指针

137 阅读2分钟

我正在参加「掘金·启航计划」

智能指针定义

在C++中,动态内存的管理是用一对运算符完成的:newdelete。new:在动态内存中为对象分配一块空间并返回一个指向该对象的指针,delete:指向一个动态独享的指针,销毁对象,并释放与之关联的内存。

动态内存管理经常会出现两种问题:一种是忘记释放内存,会造成内存泄漏;一种是尚有指针引用内存的情况下就释放了它,就会产生引用非法内存的指针。

为了更加容易(更加安全)的使用动态内存,引入了智能指针的概念。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。标准库提供的两种智能指针的区别在于管理底层指针的方法不同,shared_ptr允许多个指针指向同一个对象,unique_ptr则“独占”所指向的对象。标准库还定义了一种名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象,这三种智能指针都定义在memory头文件中。

智能指针与Rust的内存安全

智能指针只解决了一部分问题,即独占/共享所以权指针的释放、传输

没有从根本上解决C++内存安全问题。

实验

cat.h

#include<string>
#include<iostream>class Cat
{
private:
    std::string name{"mini"};
    /* data */
public:
    Cat();
    Cat(std::string);
    ~Cat();
    void cat_info() const{
        std::cout<<"cat_info name: "<<name<<std::endl;
    }
    std::string getname() const{
        return name;
    }
    void set_cat_name(const std::string &name){
        this->name = name;
    }
};
​

cat.cpp

#include "cat.h"
​
Cat::Cat(std::string name):name(name)
{
    std::cout<<"Constructor of Cat: "<<name<<std::endl;
}
​
​
Cat::Cat()
{
    std::cout<<"Constructor of Cat: "<<name<<std::endl;
}
​
​
Cat::~Cat()
{
    std::cout<<"Destructor of Cat: "<<name<<std::endl;
}

makefile

EXEC = main
CC = g++
CFLAGS = -c -Wall
​
$(EXEC) :main.o cat.o
    $(CC) -o $(EXEC) main.o cat.o
​
main.o :main.cpp cat.h
    $(CC) $(CFLAGS) main.cpp
​
cat.o :cat.cpp cat.h
    $(CC) $(CFLAGS) cat.cpp
​

代码块

main.cpp

#include"cat.h"
using namespace std;
​
int main(int argc,char *argv[]){
​
    Cat c1("c1");
    {
        Cat c2("c2");
    }
    cout<<"-------------yz--------"<<endl;
}
​
/*
Constructor of Cat: c1
Constructor of Cat: c2
Destructor of Cat: c2
-------------yz--------
Destructor of Cat: c1
*/

c2在代码块结束时即会调用析构函数,c1在函数结束时调用析构函数

raw pointer

main.cpp

#include"cat.h"
using namespace std;
​
int main(int argc,char *argv[]){
​
    Cat *c_p1 = new Cat("c_p1");
    {
        Cat *c_p2 = new Cat("c_p2");
    }
    cout<<"-------------yz--------"<<endl;
}
​
/*
Constructor of Cat: c_p1
Constructor of Cat: c_p2
-------------yz--------
*/

原始指针不需要使用时也不会调用析构函数,需要手动delete

#include"cat.h"
using namespace std;
​
int main(int argc,char *argv[]){
​
    Cat *c_p1 = new Cat("c_p1");
    {
        Cat *c_p2 = new Cat("c_p2");
        delete c_p2;
    }
    delete c_p1;
    cout<<"-------------yz--------"<<endl;
}
/*
Constructor of Cat: c_p1
Constructor of Cat: c_p2
Destructor of Cat: c_p2
Destructor of Cat: c_p1
-------------yz--------
*/

unique_ptr

  • unique_ptr可以通过get()获取地址
  • unique_ptr实现了->与*

创建

使用已有裸指针创建

#include"cat.h"
#include <memory>
using namespace std;
​
int main(int argc,char *argv[]){
​
    Cat *c_p2 = new Cat("yz");
    std::unique_ptr<Cat> u_c_p2{c_p2};
​
    c_p2->cat_info();
    u_c_p2->cat_info();
    c_p2->set_cat_name("ok");
    u_c_p2->cat_info();
​
    cout<<"-------------yz--------"<<endl;
    return 0;
}
/*
Constructor of Cat: yz
cat_info name: yz
cat_info name: yz
cat_info name: ok
-------------yz--------
Destructor of Cat: ok
*/

裸指针改变成员变量,独占指针也会跟着改变,失去了独占指针的含义。

使用new来创建

#include"cat.h"
#include <memory>
using namespace std;
​
int main(int argc,char *argv[]){
​
    std::unique_ptr<Cat> u_c_p3{new Cat("dd")};
​
    u_c_p3->cat_info();
    u_c_p3->set_cat_name("ok");
    u_c_p3->cat_info();
    cout<<"-------------yz--------"<<endl;
    return 0;
}
/*
Constructor of Cat: dd
cat_info name: dd
cat_info name: ok
-------------yz--------
Destructor of Cat: ok
*/

使用std::make_unique创建(推荐)

#include"cat.h"
#include <memory>
using namespace std;
​
int main(int argc,char *argv[]){
​
    unique_ptr<Cat> u_c_p4 = make_unique<Cat>();
    unique_ptr<int> u_i_p4 = make_unique<int>(50);
    cout<<u_c_p4.get()<<endl;
    cout<<*u_i_p4<<endl;
    cout<<u_i_p4.get()<<endl;
    u_c_p4->cat_info();
    u_c_p4->set_cat_name("ok");
    
    u_c_p4->cat_info();
    cout<<"-------------yz--------"<<endl;
    return 0;
}
/*
Constructor of Cat: mini
0x55882495beb0
50
0x55882495c2f0
cat_info name: mini
cat_info name: ok
-------------yz--------
Destructor of Cat: ok
*/

函数调用

  • unique_ptr是不可Copy,只可以Move
  • 在做函数参数或是返回值中一定要注意所有权

Passing by value

  • 需要通过std::move来转移内存拥有权
  • 如果参数直接传入std::make_unique语句,自动转换move
#include"cat.h"
#include <memory>
using namespace std;
void do_with_cat_pass_value(unique_ptr<Cat> c){
    c->cat_info(); 
}
​
int main(int argc,char *argv[]){
​
    unique_ptr<Cat> c1 = make_unique<Cat>("ff");
    do_with_cat_pass_value(move(c1));
    cout<<c1.get()<<endl;
    //c1->cat_info(); //报错,在do_with_cat_pass_value函数中已经被销毁了
    do_with_cat_pass_value(make_unique<Cat>());//自动move
    cout<<"-------------yz--------"<<endl;
    return 0;
}
/*
Constructor of Cat: ff
cat_info name: ff
Destructor of Cat: ff
0
Constructor of Cat: mini
cat_info name: mini
Destructor of Cat: mini
-------------yz--------
*/

Passing by reference

  • 如果设置参数为const则不能改变指向,比如reset()
  • reset()方法为智能指针清空方法
#include"cat.h"
#include <memory>
using namespace std;
void do_with_cat_pass_ref(unique_ptr<Cat> &c){
    c->set_cat_name("oo");
    c->cat_info(); 
    //c.reset();
}
​
int main(int argc,char *argv[]){
​
    unique_ptr<Cat> c2 = make_unique<Cat>("ff2");
    do_with_cat_pass_ref(c2);
    cout<<c2.get()<<endl;
    cout<<"-------------yz--------"<<endl;
    return 0;
}
/*
Constructor of Cat: ff2
cat_info name: oo
0x55a711dddeb0
-------------yz--------
Destructor of Cat: oo
*/

Return by value

  • 指向一个local object
  • 可以用作链式函数
#include"cat.h"
#include <memory>
using namespace std;
unique_ptr<Cat> get_unique_ptr(){
    unique_ptr<Cat> p_dog = make_unique<Cat>("Local cat");
    cout<<"unique address: "<<p_dog.get()<<endl;
    cout<<"unique address: "<<&p_dog<<endl;
    return p_dog; 
}
​
int main(int argc,char *argv[]){
    unique_ptr<Cat> p1 = get_unique_ptr();
    cout<<"unique address: "<<p1.get()<<endl;
    cout<<"-------------yz--------"<<endl;
    return 0;
}
/*
Constructor of Cat: Local cat
unique address: 0x55ceb9277eb0
unique address: 0x7ffeecbc1c80
unique address: 0x55ceb9277eb0
-------------yz--------
Destructor of Cat: Local cat
*/

shared_ptr

  • shared_ptr计数指针又称为共享指针
  • 与unique_ptr不同的是它是可以共享数据的
  • shared_ptr创建了一个计数器与类对象所指的内存相关联
  • Copy则计数器加一,销毁则计数器减一

常量类型

#include"cat.h"
#include <memory>
using namespace std;
​
​
int main(int argc,char *argv[]){
    shared_ptr<int> i_p_1 = make_shared<int>(10);
    cout<<"value : "<<*i_p_1<<endl;
    cout<<"use count: "<<i_p_1.use_count()<<endl;
​
    //copy
    shared_ptr<int> i_p_2 = i_p_1;
    cout<<"i_p_1 use count: "<<i_p_1.use_count()<<endl;
    cout<<"i_p_2 use count: "<<i_p_2.use_count()<<endl;
​
    //change
    *i_p_2 = 20;
    cout<<"i_p_1 value : "<<*i_p_1<<endl;
    cout<<"i_p_2 value : "<<*i_p_2<<endl;
​
    //i_p_2=nullptr
    shared_ptr<int> i_p_3 = i_p_1;
    i_p_2 = nullptr;
    cout<<"i_p_1 use count: "<<i_p_1.use_count()<<endl;
    cout<<"i_p_2 use count: "<<i_p_2.use_count()<<endl;
    cout<<"i_p_3 use count: "<<i_p_3.use_count()<<endl;
​
    cout<<"-------------yz--------"<<endl;
    return 0;
}
/*
value : 10
use count: 1
i_p_1 use count: 2
i_p_2 use count: 2
i_p_1 value : 20
i_p_2 value : 20
i_p_1 use count: 2
i_p_2 use count: 0
i_p_3 use count: 2
-------------yz--------
*/

自定义类型

对象的构造和析构只会发生一次,不论你有多少个指针指向这个对象

#include"cat.h"
#include <memory>
using namespace std;
​
​
int main(int argc,char *argv[]){
    shared_ptr<Cat> c_p_1 = make_shared<Cat>();
    cout<<"use count: "<<c_p_1.use_count()<<endl;
​
    //copy
    shared_ptr<Cat> c_p_2 = c_p_1;
    shared_ptr<Cat> c_p_3 = c_p_1;
    cout<<"c_p_1 use count: "<<c_p_1.use_count()<<endl;
    cout<<"c_p_2 use count: "<<c_p_2.use_count()<<endl;
    cout<<"c_p_3 use count: "<<c_p_3.use_count()<<endl;
​
    c_p_1.reset();
    cout<<"c_p_1 use count: "<<c_p_1.use_count()<<endl;
    cout<<"c_p_2 use count: "<<c_p_2.use_count()<<endl;
    cout<<"c_p_3 use count: "<<c_p_3.use_count()<<endl;
    cout<<"-------------yz--------"<<endl;
    return 0;
}
/*
Constructor of Cat: mini
use count: 1
c_p_1 use count: 3
c_p_2 use count: 3
c_p_3 use count: 3
c_p_1 use count: 0
c_p_2 use count: 2
c_p_3 use count: 2
-------------yz--------
Destructor of Cat: mini
*/

函数调用

Passing by value

在do_with_cat_pass_value函数里是copy进去的,所以在函数里有2个指针指向

#include"cat.h"
#include <memory>
using namespace std;
void do_with_cat_pass_value(shared_ptr<Cat> c){
    cout<< c->getname() <<endl;
    cout<< "func use count : " << c.use_count() <<endl;
}
​
int main(int argc,char *argv[]){
​
    shared_ptr<Cat> c1 = make_shared<Cat>("dd");
    do_with_cat_pass_value(c1);
    c1->cat_info();
    cout<< "c1 use count : " << c1.use_count() <<endl;
    cout<<"-------------yz--------"<<endl;
    return 0;
}
/*
Constructor of Cat: dd
dd
func use count : 2
cat_info name: dd
c1 use count : 1
-------------yz--------
Destructor of Cat: dd
*/

Passing by reference

#include"cat.h"
#include <memory>
using namespace std;
void do_with_cat_pass_ref(shared_ptr<Cat> &c){
    cout<< c->getname() <<endl;
    cout<< "func use count : " << c.use_count() <<endl;
}
​
int main(int argc,char *argv[]){
​
    shared_ptr<Cat> c1 = make_shared<Cat>("dd");
    do_with_cat_pass_ref(c1);
    c1->cat_info();
    cout<< "c1 use count : " << c1.use_count() <<endl;
    cout<<"-------------yz--------"<<endl;
    return 0;
}
/*
Constructor of Cat: dd
dd
func use count : 1
cat_info name: dd
c1 use count : 1
-------------yz--------
Destructor of Cat: dd
*/

unique_ptr/shared_ptr

  • 不能将shared_ptr转为unique_ptr
  • unique_ptr可以转换为shared_ptr 通过move()函数
  • 将你的函数返回unique_ptr是一种常见的设计模式,这样可以提高代码的复用度,你可以随时改变为shared_ptr
#include"cat.h"
#include <memory>
using namespace std;
unique_ptr<Cat> get_unique_ptr(){
    unique_ptr<Cat> p_dog = make_unique<Cat>("Local cat");
    return p_dog; 
}
​
int main(int argc,char *argv[]){
​
    unique_ptr<Cat> c_p_1 = make_unique<Cat>("dd");
    shared_ptr<Cat> c_p_2 = move(c_p_1);
    cout<< "c_p_2 use count : " << c_p_2.use_count() <<endl;
​
    shared_ptr<Cat> c_p_3 = get_unique_ptr();
    c_p_3->cat_info();
    cout<< "c_p_3 use count : " << c_p_3.use_count() <<endl;
​
    cout<<"-------------yz--------"<<endl;
    return 0;
}
/*
Constructor of Cat: dd
c_p_2 use count : 1
Constructor of Cat: Local cat
cat_info name: Local cat
c_p_3 use count : 1
-------------yz--------
Destructor of Cat: Local cat
Destructor of Cat: dd
*/

weak_ptr

  • weak_ptr并不拥有所有权
  • 并不能调用->和解引用*
  • weak_ptr能使用use_count(),但不会加一
  • weak_ptr可以通过lock()函数来提升为shared_ptr
#include"cat.h"
#include <memory>
using namespace std;
​
​
int main(int argc,char *argv[]){
​
    shared_ptr<Cat> s_p_1 = make_shared<Cat>("C1");
    weak_ptr<Cat> w_p_1(s_p_1);
    cout<< "s_p_1 use count : " << s_p_1.use_count() <<endl;
    cout<< "w_p_1 use count : " << w_p_1.use_count() <<endl;
    
​
    shared_ptr<Cat> c_p_2 = w_p_1.lock();
    cout<< "s_p_1 use count : " << s_p_1.use_count() <<endl;
    cout<< "w_p_1 use count : " << w_p_1.use_count() <<endl;
    cout<< "c_p_2 use count : " << c_p_2.use_count() <<endl;    
​
    cout<<"-------------yz--------"<<endl;
    return 0;
}
/*
Constructor of Cat: C1
s_p_1 use count : 1
w_p_1 use count : 1
s_p_1 use count : 2
w_p_1 use count : 2
c_p_2 use count : 2
-------------yz--------
Destructor of Cat: C1
*/

循环依赖

  • A类中有一个需求需要存储其他A类对象的信息
  • 如果使用shared_ptr,那么在销毁时会遇到循环依赖问题
  • 所以我们这里需要用一个不需要拥有所有权的指针来标记该同类对象

cat.h

#include<string>
#include<iostream>
#include<memory>
​
​
class Cat
{
private:
    std::string name{"mini"};
    std::shared_ptr<Cat> m_friend;
    /* data */
public:
    Cat();
    Cat(std::string);
    ~Cat();
    void cat_info() const{
        std::cout<<"cat_info name: "<<name<<std::endl;
    }
    std::string getname() const{
        return name;
    }
    void set_cat_name(const std::string &name){
        this->name = name;
    }
    void set_friend(std::shared_ptr<Cat> c){
        m_friend = c;
    }
};
​
#include"cat.h"
#include <memory>
using namespace std;
​
​
int main(int argc,char *argv[]){
​
    shared_ptr<Cat> C3 = make_shared<Cat>("C3");
    shared_ptr<Cat> C4 = make_shared<Cat>("C4");
​
    C3->set_friend(C4);
    C4->set_friend(C3);  //循环依赖,C3 C4不会析构
​
    cout<<"-------------yz--------"<<endl;
    return 0;
}
/*
Constructor of Cat: C3
Constructor of Cat: C4
-------------yz--------
*/

解决

将cat.h中的std::shared_ptr m_friend;改为std::weak_ptr m_friend;则能正常析构!

Constructor of Cat: C3
Constructor of Cat: C4
-------------yz--------
Destructor of Cat: C4
Destructor of Cat: C3

\