C++ 智能指针

C++11中的智能指针

概述

智能指针用于自动管理动态分配的内存,可以减少内存泄漏和野指针的风险,C++11中提供了三种智能指针。
[小知识] 野指针和悬挂指针是有区别的,野指针是未分配内存或未初始化的指针,指向的内存地址是不确定的,可以是任何地址。悬挂指针是指已经被释放或指向内存区域无效的指针。如通过new出的指针,调用delete后,没有赋值为nullptr,就成了悬挂指针。二者的不正确使用,都会导致程序崩溃。

unique_ptr(独占智能指针)

特点
  • 同一时刻只能有一个unique_ptr指向给定对象(独占性)
  • 不允许复制操作(禁用拷贝构造,赋值),但可以通过std::move转移所有权
  • 离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)
  • 访问自己成员如reset使用. ,而访问引用指针成员使用 ->
示例
std::unique_ptr<int> uptr(new int(10));  
// std::unique_ptr<int> uptr2 = uptr; // 错误,不允许复制  
std::unique_ptr<int> uptr2 = std::move(uptr); // 转移所有权

注意事项
  • 如果 unique_ptr 需要管理一个动态分配的数组,你应该使用 std::unique_ptr<T[]> 的特化版本,而不是 std::unique_ptr<T*>。这样,当 unique_ptr 被销毁时,它会使用 delete[] 而不是 delete 来释放内存
  • 由于 std::unique_ptr 不支持拷贝操作,因此不能直接将 unique_ptr 存储在 STL 容器中,因为容器通常需要元素类型支持拷贝操作。但是,你可以使用 std::vector<std::unique_ptr> 这样的组合,因为 vector 支持元素的移动操作
  • 当你的 unique_ptr 指向一个自定义类型时,确保该类型的析构函数被正确定义,以便在 unique_ptr 销毁时能够正确地清理资源。
总结

它几乎和原始指针相同的性能,并且在C++标准库容器中可以使用,但是不能使用在算法中.
向容器中添加unique_ptr的实例,是非常高效的,因为move构造器的使用减少了不必要的拷贝。
示例:

unique_ptr<Song> SongFactory(const std::wstring& artist, const std::wstring& title)
{
    // 隐式move转换
    return make_unique<Song>(artist, title);
}

void MakeSongs()
{
    // Create a new unique_ptr with a new object.
    auto song = make_unique<Song>(L"Mr. Children", L"Namonaki Uta");

    // Use the unique_ptr.
    vector<wstring> titles = { song->title };

    // Move raw pointer from one unique_ptr to another.
    unique_ptr<Song> song2 = std::move(song);

    // Obtain unique_ptr from function that returns by value.
    auto song3 = SongFactory(L"Michael Jackson", L"Beat It");
}

void SongVector()
{
    vector<unique_ptr<Song>> songs;
    // Create a few new unique_ptr<Song> instances
    // and add them to vector using implicit move semantics.
    songs.push_back(make_unique<Song>(L"B'z", L"Juice")); 
    songs.push_back(make_unique<Song>(L"Namie Amuro", L"Funky Town")); 
    songs.push_back(make_unique<Song>(L"Kome Kome Club", L"Kimi ga Iru Dake de")); 
    songs.push_back(make_unique<Song>(L"Ayumi Hamasaki", L"Poker Face"));
    // 通过const引用传递,尽量避免拷贝,如果是值传递,因为unique_ptr的拷贝构造是delete,所以会报错
    for (const auto& song : songs)
    {
        wcout << L"Artist: " << song->artist << L"   Title: " << song->title << endl; 
    }    
}
// Create a unique_ptr to an array of 5 integers.
auto p = make_unique<int[]>(5);
// Initialize the array.
for (int i = 0; i < 5; ++i)
{
    p[i] = i;
    wcout << p[i] << endl;
}
// C.T 可以创建一个unique_ptr的数组,但是不能make_unique来初始化这个数组元素

shared_ptr(共享智能指针)

特点
  • 多个shared_ptr对象可以指向同一个对象,使用引用计数机制。
  • 每当有一个新的shared_ptr指向对象时,引用计数加1;当shared_ptr被销毁或重置时,引用计数减1。
  • 当引用计数为0时,自动删除指向的对象。
  • 可以通过值传递在函数中使用
为什么优先推荐使用make_shared
  • 异常安全:当使用 new 关键字和 std::shared_ptr 的构造函数来创建对象时,如果对象的构造函数抛出异常,那么已经为控制块(即 std::shared_ptr 内部用于管理引用计数和删除器的数据结构)分配的内存将无法释放,从而导致内存泄漏。然而,make_shared 通过在一个操作中同时分配控制块和资源的内存来避免这个问题,从而保证了异常安全性。
  • 减少构造开销:make_shared 通过一次内存分配操作来同时分配控制块和资源的内存,而不是像传统的做法那样分两次进行。这种一次性的内存分配可以减少内存分配的开销,并可能提高内存局部性,从而提高程序的性能。
    总的来说,make_shared 是一个在创建 std::shared_ptr 对象时应该优先考虑使用的函数,因为它既高效又安全。
示例
std::shared_ptr<int> sp1(new int(10));  
std::shared_ptr<int> sp2 = sp1; // 引用计数加1  
// ... 当sp1和sp2都离开作用域时,引用计数减至0,自动释放内存

标准容器中的应用

vector<shared_ptr<Song>> v {
  make_shared<Song>(L"Bob Dylan", L"The Times They Are A Changing"),
  make_shared<Song>(L"Aretha Franklin", L"Bridge Over Troubled Water"),
  make_shared<Song>(L"Thalía", L"Entre El Mar y Una Estrella")
};

vector<shared_ptr<Song>> v2;
remove_copy_if(v.begin(), v.end(), back_inserter(v2), [] (shared_ptr<Song> s) 
{
    return s->artist.compare(L"Bob Dylan") == 0;
});

for (const auto& s : v2)
{
    wcout << s->artist << L":" << s->title << endl;
}

使用cast指针

vector<shared_ptr<MediaAsset>> assets {
  make_shared<Song>(L"Himesh Reshammiya", L"Tera Surroor"),
  make_shared<Song>(L"Penaz Masani", L"Tu Dil De De"),
  make_shared<Photo>(L"2011-04-06", L"Redmond, WA", L"Soccer field at Microsoft.")
};

vector<shared_ptr<MediaAsset>> photos;

copy_if(assets.begin(), assets.end(), back_inserter(photos), [] (shared_ptr<MediaAsset> p) -> bool
{
    // Use dynamic_pointer_cast to test whether
    // element is a shared_ptr<Photo>.
    shared_ptr<Photo> temp = dynamic_pointer_cast<Photo>(p);
    return temp.get() != nullptr;
});

for (const auto&  p : photos)
{
    // We know that the photos vector contains only 
    // shared_ptr<Photo> objects, so use static_cast.
    wcout << "Photo location: " << (static_pointer_cast<Photo>(p))->location << endl;
}

shared_ptr作为参数的场景

  • 值传递: 此时一定是调用的是拷贝构造函数, 引用计数增加1,这个操作的开销并不大, 如果传递的对象多了,那么开销应该还是很大. 当被调用的函数需要是一个owner的时候,使用此方法,
  • 引用传递. 此时引用计数不会增加,只要caller的指针还有效,在fun里面就可以访问该指针,当然在fun里面还可以创建一个新的shared_ptr. 当caller 不知道 被调用方 why?,或者处于性能目的使用,该场景。
  • 传递实际的指针(shared_ptr 封装的)或者实际对象的引用,这种方式可以在被调用者函数里面使用该对象,但是不会拓展它的生命周期,如果,被调用函数内部根据这个原始指针创建了一个新的shared_ptr,那么它和之前的shared_ptr 是相互独立的,有可能会造成一个raw指针被多次释放,导致崩溃的情况。

weak_ptr(弱引用智能指针)

特点
  • 弱引用,不会增加引用计数。
  • 用于解决shared_ptr的循环引用问题。
  • 不能直接访问对象,需要通过lock方法尝试获取shared_ptr。

示例

std::shared_ptr<int> sp(new int(10));  
std::weak_ptr<int> wp = sp;  
if (auto sp2 = wp.lock()) {  
    // 成功获取shared_ptr,可以安全访问对象  
} else {  
    // 原始对象可能已被销毁  
}
#include <iostream>
#include <memory>
#include <functional>

class SharedPtrClass
{
public:
	SharedPtrClass() {
		std::cout << "SharedPtrClass()" << std::endl;
	}

	~SharedPtrClass()
	{
		std::cout << "~SharedPtrClass()" << std::endl;
	}

	SharedPtrClass(int, std::string) {
	
	}
};


int main()
{
	// create a shared_ptr with constructor.
	std::shared_ptr<int> sptr1(new int(12));

	// create a shared_ptr with copy constrcutor.
	std::shared_ptr<int> sptr2 = sptr1;

	std::cout << "sptr1 use_count: " << sptr1.use_count() << std::endl;

	std::cout << "sptr2 use_count: " << sptr2.use_count() << std::endl;

	// create a shared_ptr with move constructor
	std::shared_ptr<int> sptr3 = std::move(sptr2);

	std::cout << "sptr3 use_count: " << sptr3.use_count() << std::endl;

	// create a shared_ptr with make_shared
	std::shared_ptr<int> sptr4 = std::make_shared<int>(10);
	std::cout << "sptr4 use_count: " << sptr4.use_count() << " data:" << *sptr4 << std::endl;

	// create a shared_ptr pointed to class with make_shared

	std::shared_ptr<SharedPtrClass> sptr5 = std::make_shared<SharedPtrClass>(11, "hello");
	std::cout << "sptr5 use_count: " << sptr5.use_count() << std::endl;

	std::shared_ptr<SharedPtrClass> sptr6 = sptr5;
	sptr6.reset();
	std::cout << "sptr5 use_count: " << sptr5.use_count() << std::endl;

	// use custom deletor
	std::shared_ptr<SharedPtrClass> sptr7(new SharedPtrClass(), [](SharedPtrClass* sp) {
		delete sp;
		sp = nullptr;
	});

	// must use custom deletor for array
	std::shared_ptr<SharedPtrClass> sptr8(new SharedPtrClass[3], [](SharedPtrClass* sp) {
		delete[] sp;
		sp = nullptr;
		});

	// use default_delete provide by c++
	std::shared_ptr<SharedPtrClass> sptr9(new SharedPtrClass[3], std::default_delete<SharedPtrClass[]>());

	
	// unique_ptr , copy constructor is not support
	std::unique_ptr<int> uptr1(new int(4));
	std::unique_ptr<int> uptr2 = std::move(uptr1);
	uptr2.reset(new int(3));

	// use lambda to custom deletor
	using funcPtr = void(*)(SharedPtrClass*);
	std::unique_ptr<SharedPtrClass, funcPtr> uptr3(new SharedPtrClass(), [](SharedPtrClass* sp) {
		delete sp;
	});

	// note: if lambda capture external variable, it can be a functor.
	std::unique_ptr<SharedPtrClass, std::function<void(SharedPtrClass*)>> uptr4(new SharedPtrClass(), [=](SharedPtrClass* sp) {
		delete sp;
	});

	// unique_ptr can manage a arrary
	std::unique_ptr<SharedPtrClass[]> uptr5(new SharedPtrClass[3]);

	// after c++11 version support follow, if not, please use above delete
	std::shared_ptr<SharedPtrClass[]> uptr6(new SharedPtrClass[2]);
}
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

class Controller 
{
public:
    int Num;
    wstring Status;
    vector<weak_ptr<Controller>> others;
    explicit Controller(int i) : Num(i) , Status(L"On")
    {
        wcout << L"Creating Controller" << Num << endl;
    }

    ~Controller()
    {
        wcout << L"Destroying Controller" << Num << endl;
    }

    // Demonstrates how to test whether the  
    // pointed-to memory still exists or not. 
    void CheckStatuses() const
    {
        for_each(others.begin(), others.end(), [] (weak_ptr<Controller> wp)
        {
            try
            {
                auto p = wp.lock();
                wcout << L"Status of " << p->Num << " = " << p->Status << endl;
            }

            catch (bad_weak_ptr b)
            {
                wcout << L"Null object" << endl;
            }                
        });
    }
};

void RunTest()
{
    vector<shared_ptr<Controller>> v;

    v.push_back(shared_ptr<Controller>(new Controller(0)));
    v.push_back(shared_ptr<Controller>(new Controller(1)));
    v.push_back(shared_ptr<Controller>(new Controller(2)));
    v.push_back(shared_ptr<Controller>(new Controller(3)));
    v.push_back(shared_ptr<Controller>(new Controller(4)));

    // Each controller depends on all others not being deleted. 
    // Give each controller a pointer to all the others. 
    for (int i = 0 ; i < v.size(); ++i)
    {
        for_each(v.begin(), v.end(), [v,i] (shared_ptr<Controller> p)
        {
            if(p->Num != i)
            {
                v[i]->others.push_back(weak_ptr<Controller>(p));
                wcout << L"push_back to v[" << i << "]: " << p->Num << endl;
            }
        });        
    }

    for_each(v.begin(), v.end(), [](shared_ptr<Controller>& p)
    {
        wcout << L"use_count = " << p.use_count() << endl;
        p->CheckStatuses();
    });
}

int main()
{    
    RunTest(); 
    wcout << L"Press any key" << endl;
    char ch;
    cin.getline(&ch, 1);
}
总述

对于sizeof(unique_ptr) == 8 ,而对于sizeof(shared_ptr) == 16(一个是指针大小,另一个是共享控制块(包含引用计数)

最后,以一首诗收尾:
指针虽小责任大,
弃用delete心无挂。
智能管理无烦恼,
代码之美如诗画

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值