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心无挂。
智能管理无烦恼,
代码之美如诗画