版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.jianshu.com/p/b8d6b10da667
智能指针
1.什么是智能指针
最近有个段子
- C语言:搬石头砸自己的脚;
- C++:搬石头砸自己的脚,也可能砸别人的脚;
- python:点个按钮,自动搬石头;
在三大常用语言中,C/C++, java,python中,通常情况下C/C++性能最好,但是大部分开发这都喜欢java和python,其中主要的原因之一是C/C++缺少智能内存回收,在复杂的系统中,经常遇到一个常见的问题 -- 内存泄露。
对于一个程序员,码农最头大的事情就是内存泄露。君不见,内存泄露吼三吼。
C++的开发这都会想,有没有一种方式能像java和python一样方便,系统自动释放呢?
unique_ptr 是C++ 11 提供的用于防止内存泄漏的智能指针中的一种实现,独享被管理对象指针所有权的智能指针(shared_ptr 下次在分享)。
2.智能指针的实现
unique_ptr定义<memory>头文件中
template <class T, class D = default_delete<T>> class unique_ptr;
template <class T, class D> class unique_ptr<T[],D>;
std::unique_ptr
是通过指针占有并管理另一对象,并在 unique_ptr
离开作用域时释放该对象的智能指针。
在下列两者之一发生时用关联的删除器释放对象:
通过调用 get_deleter()(ptr) ,用潜在为用户提供的删除器释放对象。默认删除器用 delete 运算符,它销毁对象并解分配内存。
unique_ptr
亦可以不占有对象,该情况下称它为空 (empty)。
std::unique_ptr
有两个版本:
-
管理个对象(例如以 new 分配)
-
管理动态分配的对象数组(例如以 new[] 分配)
类满足可移动构造 (MoveConstructible) 和可移动赋值 (MoveAssignable) 的要求,但不满足可复制构造 (CopyConstructible) 或可复制赋值 (CopyAssignable) 的要求。
unique_ptr的使用
备注:make_unique 是C++14的新特性,如果使用C++11编译,请把make_unique改成std::unique_ptr<T>的形式。
#include <iostream>
#include <vector>
#include <memory>
#include <cstdio>
#include <fstream>
#include <cassert>
#include <functional>
struct B {
virtual void bar() { std::cout << "B::bar\n"; }
virtual ~B() = default;
};
struct D : B
{
D() { std::cout << "D::D\n"; }
~D() { std::cout << "D::~D\n"; }
void bar() override { std::cout << "D::bar\n"; }
};
// 消费 unique_ptr 的函数能以值或以右值引用接收它
std::unique_ptr<D> pass_through(std::unique_ptr<D> p)
{
p->bar();
return p;
}
void close_file(std::FILE* fp) { std::fclose(fp); }
int main()
{
std::cout << "unique ownership semantics demo\n";
{
auto p = std::make_unique<D>(); // p 是占有 D 的 unique_ptr
auto q = pass_through(std::move(p));
assert(!p); // 现在 p 不占有任何内容并保有空指针
q->bar(); // 而 q 占有 D 对象
} // ~D 调用于此
std::cout << "Runtime polymorphism demo\n";
{
std::unique_ptr<B> p = std::make_unique<D>(); // p 是占有 D 的 unique_ptr
// 作为指向基类的指针
p->bar(); // 虚派发
std::vector<std::unique_ptr<B>> v; // unique_ptr 能存储于容器
v.push_back(std::make_unique<D>());
v.push_back(std::move(p));
v.emplace_back(new D);
for(auto& p: v) p->bar(); // 虚派发
} // ~D called 3 times
std::cout << "Custom deleter demo\n";
std::ofstream("demo.txt") << 'x'; // 准备要读的文件
{
std::unique_ptr<std::FILE, void (*)(std::FILE*) > fp(std::fopen("demo.txt", "r"),
close_file);
if(fp) // fopen 可以打开失败;该情况下 fp 保有空指针
std::cout << (char)std::fgetc(fp.get()) << '\n';
} // fclose() 调用于此,但仅若 FILE* 不是空指针
// (即 fopen 成功)
std::cout << "Custom lambda-expression deleter demo\n";
{
std::unique_ptr<D, std::function<void(D*)>> p(new D, [](D* ptr)
{
std::cout << "destroying from a custom deleter...\n";
delete ptr;
}); // p 占有 D
p->bar();
} // 调用上述 lambda 并销毁 D
std::cout << "Array form of unique_ptr demo\n";
{
std::unique_ptr<D[]> p{new D[3]};
} // 调用 ~D 3 次
}
运行结果:
unique ownership semantics demo
D::D
D::bar
D::bar
D::~D
Runtime polymorphism demo
D::D
D::bar
D::D
D::D
D::bar
D::bar
D::bar
D::~D
D::~D
D::~D
Custom deleter demo
x
Custom lambda-expression deleter demo
D::D
D::bar
destroying from a custom deleter...
D::~D
Array form of unique_ptr demo
D::D
D::D
D::D
D::~D
D::~D
D::~D
智能指针使用总结:
- 智能指针自己管理内存的声明周期;
- 智能指针在构造是可以制定对应了销毁函数;
进阶用法:
在上述的智能指针使用中,我们通过传入delete的函数,这里可以采用更优雅的方式:通过结构体的运算符重载达到delete函数的效果
#include <iostream>
#include <memory>
using namespace std;
typedef struct _package{
unsigned char* data;
int length;
}package_t;
void release_package(package_t* package){
cout<<"release_package"<<endl;
if(!package){
return;
}
if(package->data){
delete[] package->data;
}
delete package;
}
struct package_destuctor{
void operator()(package_t* package){
release_package(package);
}
};
// unique_ptr 不能拷贝或者复制
int main(){
unique_ptr<package_t, decltype(release_package)*> ret1(new package_t(), release_package);
unique_ptr<package_t, package_destuctor> ret(new package_t());
return 0;
}
运行结果:
两种方式都正常release数据
release_package
release_package
进阶二
unique_ptr作为形参时,必须保证不能发生COPY
例如:
unique_ptr<T> uptr(new T);
show_ptr(uptr);
这种调用会出错,原因在于uptr作为形参时,会发生copy,而unique_ptr不允许Copy。
#include <iostream>
#include <memory>
using namespace std;
class MyTest{
public:
MyTest(const string & name)
:_name(name){
cout<<"MyTest:"<<_name<<endl;
}
MyTest(const MyTest & another){
_name = another._name;
cout<<another._name<<"copyStruct "<<_name<<endl;
}
MyTest & operator =(const MyTest & another){
if(&another==this)
return *this;
this->_name=another._name;
cout<<another._name<<"copyAssin to "<<_name<<endl;
}
~MyTest(){
cout<<"~MyTest:"<<_name<<endl;
}
//private:
string _name;
};
//!例外:
//①返回一个即将被销毁的uniptr
unique_ptr<MyTest> retDying(string param){
return unique_ptr<MyTest>(new MyTest(param));
}
//②返回一个局部对象;
unique_ptr<MyTest> retTemp(string param){
unique_ptr<MyTest> pTemp(new MyTest(param));
return pTemp;
}
//unique_ptr可以作为形参,必须保证不能发生copy
unique_ptr<int> show(unique_ptr<int> up){
cout<<*up<<endl;
return up;
}
//不能删除unique中的指针,如果删除,智能指针会报错
void release(unique_ptr<MyTest>& ptr){
if(!ptr){
cout<<"delete_ptr ptr is null"<<endl;
return;
}
auto p = ptr.get();
//delete p;
}
// unique_ptr 不能拷贝或者复制
int main(){
unique_ptr<MyTest> ret1 = retDying("dying");
cout<<(*ret1)._name<<endl;
unique_ptr<int> pCount(new int(10));
//unique_ptr可以作为形参,必须保证不能发生copy,pCount不能当做参数,可以使用转移或者move
show(unique_ptr<int>(new int(10)));
show(move(pCount));
unique_ptr<MyTest> ret2 = retTemp("temp");
cout<<(*ret2)._name<<endl;
//如果传ret,必须声明为引用,如果不声明引用,必须则不能使用ret1作为参数, 可以unique_ptr<int> retp(ret1.release())
release(ret1);
return 0;
}
结果如下:
MyTest:dying
dying
10
10
MyTest:temp
temp
~MyTest:temp
~MyTest:dying
unique_ptr之release重点声明
unique_ptr中的release方法,第一眼看过去,是释放内存,其实并不是,并不是,并不是,重要的事情说三遍
看unique_ptr的release函数声明:
pointer release() noexcept;
其功能是当前的智能指针释放对持有指针的控制,并返回持有的指针,其含义是:release之后,大爷不管了,返回的指针你自己玩吧, 千万不要想当然的认为是释放内存,只是释放控制权!!!
网友评论