一、智能指针(smart pointer)
在前面的文章分析中对智能指针分析的还是比较多的,这里把一具体的遗漏以及一些新的感悟再总结一下,以之为鉴。什么是智能指针?在C/C++中,普通指针的管理,一直是贯穿整个编程过程的难点和痛点。谈到指针,多少人都是摇头。那么有没有解决的方式和改进的方法呢。俗话说得好,有问题就会有解决问题的人。智能指针虽然说不能够达到一劳永逸的解决原生指针的安全管理问题,但毕竟朝着改进整个原生指针的安全性上迈进了一大步。这也是智能指针在整个STL体系里不断的演进的原因。
大路不平旁人铲啊。
二、STL中的智能指针
在STL中的智能指针主要分成两大类即shared_ptr共享式和unique_ptr专有式或者说单一拥有。在更早期的STL中,提供过auto_ptr这种智能指针,但现在基本已经废弃掉了。
1、共享式(shared_ptr)
这种又有shared_ptr 和weak_ptr两种常见的智能指针。两种指针的出现其实是为了解决一个问题,也就是如果只有shared_ptr的情况下,如果多个智能指针对象互相持有的问题。这在种情况下,互相持有的智能指针因为彼此拥有对方的指针而使得相关的引用计数无法清空,从而在某种程度上造成内存泄露。而增加weak_ptr后,相互持有的智能指针中,一方可以是weak_ptr,它不会引起计数器的变化。但在使用前需要对其进行升级到shared_ptr的操作,如果为空,则表明相关资源已经释放。从宏观应用上来讲,没有达到根本的设计思想,但毕竟是将整个开发的安全度增加了许多,还是得大于失的。
另外一种情况下,是只是想把智能指针暴露给别人使用,但却不想让对方完成拥有,也可以采用使用weak_ptr的方式。这样,只要相关的资源没有被释放,就允许外部接口的使用。但却无法影响内部的安全性。
2、专有式(unique_ptr)
不过,在实际的开发中,大家可能也遇到过这种问题,就是说这个数据资源只想独占,而不允许别人来应用。在早期,一般可能通过类似锁机制的方式来处理类似的操作。但在新c++11后,提供了unique_ptr,通过move来达到资源的转移而不是资源的共享。换句话说,资源只能从一个控制者的手中转到另外一个控制者手中,否则一旦不再使用就会被释放。
一定需要明白的是,资源只能转移,不允许复制或者多人控制。
智能指针在多线程下一般认为是不安全的,如果想确保在多线程下操作的安全,请使用锁等机制来保障相关的data race。
需要说明的是,在c++11中,对数组应用智能指针还是稍微有一些不足,这需要自己来实现deleter,但在更高版本的c++17以后,智能指针对数组的支持也已经完成。这些不断迭代和更新的c++的最新的库的,就是支持着c++不断的向着未来前进的火种。
三、源码分析
看一下STL中善于智能指针的实现源码:
先看一下定义:
template< class T > class shared_ptr;
template<
class T,
class Deleter = std::default_delete<T>
> class unique_ptr;
template <
class T,
class Deleter
> class unique_ptr<T[], Deleter>;
再看一下源码定义:
// CLASS TEMPLATE unique_ptr SCALAR
template<class _Ty,
class _Dx> // = default_delete<_Ty>
class unique_ptr
: public _Unique_ptr_base<_Ty, _Dx>
{ // non-copyable pointer to an object
public:
typedef _Unique_ptr_base<_Ty, _Dx> _Mybase;
typedef typename _Mybase::pointer pointer;
typedef _Ty element_type;
typedef _Dx deleter_type;
using _Mybase::get_deleter;
template<class _Dx2 = _Dx,
_Unique_ptr_enable_default_t<_Dx2> = 0>
constexpr unique_ptr() noexcept
: _Mybase(pointer())
{ // default construct
}
template<class _Dx2 = _Dx,
_Unique_ptr_enable_default_t<_Dx2> = 0>
constexpr unique_ptr(nullptr_t) noexcept
: _Mybase(pointer())
{ // null pointer construct
}
//空指针的处理
unique_ptr& operator=(nullptr_t) noexcept
{ // assign a null pointer
reset();
return (*this);
}
template<class _Dx2 = _Dx,
_Unique_ptr_enable_default_t<_Dx2> = 0>
explicit unique_ptr(pointer _Ptr) noexcept
: _Mybase(_Ptr)
{ // construct with pointer
}
template<class _Dx2 = _Dx,
enable_if_t<is_constructible_v<_Dx2, const _Dx2&>, int> = 0>
unique_ptr(pointer _Ptr, const _Dx& _Dt) noexcept
: _Mybase(_Ptr, _Dt)
{ // construct with pointer and (maybe const) deleter&
}
template<class _Dx2 = _Dx,
enable_if_t<conjunction_v<negation<is_reference<_Dx2>>,
is_constructible<_Dx2, _Dx2>>, int> = 0>
unique_ptr(pointer _Ptr, _Dx&& _Dt) noexcept
: _Mybase(_Ptr, _STD move(_Dt))
{ // construct by moving deleter
}
template<class _Dx2 = _Dx,
enable_if_t<conjunction_v<is_reference<_Dx2>,
is_constructible<_Dx2, remove_reference_t<_Dx2>>>, int> = 0>
unique_ptr(pointer, remove_reference_t<_Dx>&&) = delete;
unique_ptr(unique_ptr&& _Right) noexcept
: _Mybase(_Right.release(),
_STD forward<_Dx>(_Right.get_deleter()))
{ // construct by moving _Right
}
template<class _Ty2,
class _Dx2,
enable_if_t<conjunction_v<negation<is_array<_Ty2>>,
is_convertible<typename unique_ptr<_Ty2, _Dx2>::pointer, pointer>,
conditional_t<is_reference_v<_Dx>, is_same<_Dx2, _Dx>, is_convertible<_Dx2, _Dx>>
>, int> = 0>
unique_ptr(unique_ptr<_Ty2, _Dx2>&& _Right) noexcept
: _Mybase(_Right.release(),
_STD forward<_Dx2>(_Right.get_deleter()))
{ // construct by moving _Right
}
#if _HAS_AUTO_PTR_ETC
template<class _Ty2,
enable_if_t<conjunction_v<is_convertible<_Ty2 *, _Ty *>,
is_same<_Dx, default_delete<_Ty>>>, int> = 0>
unique_ptr(auto_ptr<_Ty2>&& _Right) noexcept
: _Mybase(_Right.release())
{ // construct by moving _Right
}
#endif /* _HAS_AUTO_PTR_ETC */
template<class _Ty2,
class _Dx2,
enable_if_t<conjunction_v<negation<is_array<_Ty2>>,
is_assignable<_Dx&, _Dx2>,
is_convertible<typename unique_ptr<_Ty2, _Dx2>::pointer, pointer>
>, int> = 0>
unique_ptr& operator=(unique_ptr<_Ty2, _Dx2>&& _Right) noexcept
{ // assign by moving _Right
reset(_Right.release());
this->get_deleter() = _STD forward<_Dx2>(_Right.get_deleter());
return (*this);
}
unique_ptr& operator=(unique_ptr&& _Right) noexcept
{ // assign by moving _Right
if (this != _STD addressof(_Right))
{ // different, do the move
reset(_Right.release());
this->get_deleter() = _STD forward<_Dx>(_Right.get_deleter());
}
return (*this);
}
void swap(unique_ptr& _Right) noexcept
{ // swap elements
_Swap_adl(this->_Myptr(), _Right._Myptr());
_Swap_adl(this->get_deleter(), _Right.get_deleter());
}
~unique_ptr() noexcept
{ // destroy the object
if (get() != pointer())
{
this->get_deleter()(get());
}
}
_NODISCARD add_lvalue_reference_t<_Ty> operator*() const
{ // return reference to object
return (*get());
}
_NODISCARD pointer operator->() const noexcept
{ // return pointer to class object
return (this->_Myptr());
}
_NODISCARD pointer get() const noexcept
{ // return pointer to object
return (this->_Myptr());
}
explicit operator bool() const noexcept
{ // test for non-null pointer
return (get() != pointer());
}
pointer release() noexcept
{ // yield ownership of pointer
pointer _Ans = get();
this->_Myptr() = pointer();
return (_Ans);
}
void reset(pointer _Ptr = pointer()) noexcept
{ // establish new pointer
pointer _Old = get();
this->_Myptr() = _Ptr;
if (_Old != pointer())
{
this->get_deleter()(_Old);
}
}
//可以看到这两个赋值的符号被禁止了
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
};
在前面也提到过,智能指针其实就是使用引用计数器,源码中就可以看到相关的计数器的身影。
四、实例
看一下一个相关的实例(cppreference.com):
#include <iostream>
#include <vector>
#include <memory>
#include <cstdio>
#include <fstream>
#include <cassert>
#include <functional>
// helper class for runtime polymorphism demo below
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"; }
};
// a function consuming a unique_ptr can take it by value or by rvalue reference
std::unique_ptr<D> pass_through(std::unique_ptr<D> p)
{
p->bar();
return p;
}
// helper function for the custom deleter demo below
void close_file(std::FILE* fp) { std::fclose(fp); }
// unique_ptr-based linked list demo
struct List {
struct Node {
int data;
std::unique_ptr<Node> next;
Node(int data) : data{data}, next{nullptr} {}
};
List() : head{nullptr} {};
// N.B. iterative destructor to avoid stack overflow on long lists
~List() { while(head) head = std::move(head->next); }
// copy/move and other APIs skipped for simplicity
void push(int data) {
auto temp = std::make_unique<Node>(data);
if(head) temp->next = std::move(head);
head = std::move(temp);
}
private:
std::unique_ptr<Node> head;
};
int main()
{
std::cout << "1) Unique ownership semantics demo\n";
{
auto p = std::make_unique<D>(); // p is a unique_ptr that owns a D
auto q = pass_through(std::move(p));
assert(!p); // now p owns nothing and holds a null pointer
q->bar(); // and q owns the D object
} // ~D called here
std::cout << "2) Runtime polymorphism demo\n";
{
std::unique_ptr<B> p = std::make_unique<D>(); // p is a unique_ptr that owns a D
// as a pointer to base
p->bar(); // virtual dispatch
std::vector<std::unique_ptr<B>> v; // unique_ptr can be stored in a container
v.push_back(std::make_unique<D>());
v.push_back(std::move(p));
v.emplace_back(new D);
for(auto& p: v) p->bar(); // virtual dispatch
} // ~D called 3 times
std::cout << "3) Custom deleter demo\n";
std::ofstream("demo.txt") << 'x'; // prepare the file to read
{
std::unique_ptr<std::FILE, decltype(&close_file)> fp(std::fopen("demo.txt", "r"),
&close_file);
if(fp) // fopen could have failed; in which case fp holds a null pointer
std::cout << (char)std::fgetc(fp.get()) << '\n';
} // fclose() called here, but only if FILE* is not a null pointer
// (that is, if fopen succeeded)
std::cout << "4) 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 owns D
p->bar();
} // the lambda above is called and D is destroyed
std::cout << "5) Array form of unique_ptr demo\n";
{
std::unique_ptr<D[]> p(new D[3]);
} // calls ~D 3 times
std::cout << "6) Linked list demo\n";
{
List l;
for(long n = 0; n != 1'000'000; ++n) l.push(n);
std::cout << "destroying 1'000'000 nodes... ";
} // destroys all 1 million nodes
std::cout << "Done.\n";
}
运行结果:
1) Unique ownership semantics demo
D::D
D::bar
D::bar
D::~D
2) Runtime polymorphism demo
D::D
D::bar
D::D
D::D
D::bar
D::bar
D::bar
D::~D
D::~D
D::~D
3) Custom deleter demo
x
4) Custom lambda-expression deleter demo
D::D
D::bar
destroying from a custom deleter...
D::~D
5) Array form of unique_ptr demo
D::D
D::D
D::D
D::~D
D::~D
D::~D
6) Linked list demo
destroying 1'000'000 nodes... Done.
上面的例子来自于https://en.cppreference.com/w/cpp/memory/unique_ptr,cpp的权威网站。
五、总结
在学过Java或者其它语言的GC机制后,应该就会明白内存自动回收的基本基理和相关的实现手段和发展历程。其实,计数器这种方式相对来说是一种比较低级的内存管理方式,虽然对c++来说可能是一种重大的跨越,但对整个内存管理的机制来说,它仍然是一个比较初步的方式,它在多线程应用中,还是有着这样那样的细节的小问题需要处理。
更进一步来分析,这和其它语言中(如Rust)中的生命周期和使用权又有不小的可以理解的关系或者说类比的关系,从语言宏观的角度看,所有的语言都或多或少的有着相通的影子,从这一点来看语言的学习,才能更好的把握学习的重点和精髓,有的放矢。
但是,作为一种进步,无疑对开发者来说一种利好,希望这种利好不断的出现,c++的希望就会越来越大。
努力吧,归来的少年!
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)