Skip to content

动态内存

约 1803 个字 270 行代码 7 张图片 预计阅读时间 12 分钟

  • 静态内存保存局部static对象,类static数据成员以及任何定义在任何函数之外的变量
  • 全局对象程序启动时分配,在程序结束时销毁

动态内存与智能指针

  • 每个程序有一个内存池,叫做自由空间

  • 动态对象的生存期由程序来控制

  • 智能指针负责自动释放所指向的对象

  • share_ptr允许多个指针指向同一个对象
  • unique_ptr则“独占”所指向的对象
  • weak_ptr是一种弱引用,指向shared_ptr所管理的对象

shared_ptr类

  • 是一个模板,默认初始化的智能指针中保存着一个空指针

make_shared

  • 最安全的分配和使用动态内存的方法
  • 在动态内存中分配一个对象并初始化它;返回指向该对象的shared_ptr
C++
shared_ptr<int> p3 = make_shared<int>(42);

shared_ptr的拷贝和赋值

  • 进行拷贝或赋值操作时,每个shared_ptr会记录有多少个其他shared_ptr指向相同的对象
C++
auto p = make_shared<int>(42);  // p指向的对象只有p一个引用者
auto q(p);                      // p和q指向相同对象;该对象有两个引用者
  • 用一个shared_ptr初始化另一个shared_ptr,作为参数传递,作为返回值返回;所关联的计数器会递增
  • 为shared_ptr赋新值,shared_ptr被销毁;计数器递减;计数器变为0,指针自动释放对象
  • shared_ptr自动销毁管理的对象;自动释放相关联的内存

引用计数增加

  • 一个shared_ptr初始化另一个shared_ptr,新的shared_ptr增加
  • 参数传递
  • 返回一个shared_ptr

引用计数减少

  • 一个shared_ptr初始化另一个shared_ptr,旧的shared_ptr较少
  • 被销毁

直接管理内存

new

  • 动态分配const对象,必须进行初始化;返回的指针是一个指向常量的指针
C++
const int *pci = new const int(1024);
const string *pcs = new const string
  • 需要显示地释放内存

share_ptr与new结合

  • 使用一个内置指针访问一个智能指针所负责的对象是危险的
  • 不要使用get初始化另一个智能指针或为智能指针赋值
C++
shared_ptr<int> p(new int(42));
int *q = p.get();   // 不要让q管理的指针被释放
{
    shared_ptr<int> (q);    // 两个独立的shared_ptr指向相同的内存
}   // 程序块结束;q指向的内存被释放
int foo = *p;   // p的内存已经被释放了

智能指针和异常

删除器

  • 完成对share_ptr中保存的指针进行释放
C++
void end_connection(connection *p) { disconnect(*p); }
void f(destination &d) 
{
    connection c = connect(&d);
    share_ptr<connection> p(&c, end_connection);
}

智能指针陷阱

  • 不使用相同的内置指针值初始化或reset多个智能指针
  • 不使用get初始化或reset另一个智能指针
  • 使用get返回的指针,最后一个对应的智能指针销毁后,指针变为无效
  • 使用智能指针管理的资源不是new分配的内存;传递一个删除器

unique_ptr

  • 某个时刻智能有一个unique_ptr指向一个给定对象
C++
unique_ptr<double> p1;
unique_ptr<int> p2(new int(42));
  • unique_ptr不支持拷贝和赋值;只指向一个对象

所有权转移

C++
// 所有权从p1转给p2
unique_ptr<string> p2(p1.release());
unique_ptr<string> p3(new string("Trex"));
// 所有权从p3转给p2
p2.reset(p3.release()); // reset释放了p2原来指向的内存

传递unique_ptr和返回unique_ptr

  • 我们可以拷贝或赋值一个将要被销毁的unique_ptr
C++
unique_ptr<int> clone(int p)
{
    return unique_ptr<int> (new int(p));
}

// 返回局部对象的拷贝
unique_ptr<int> clone(int p)
{
    unique_ptr<int> ret(new int(p));
    ...
    return ret;
}

向unique_ptr传递删除器

  • 使用了函数指针类型
C++
void f(destiation &d)
{
    connection c = connect(&d);
    unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection);
}

weak_ptr

  • 确定shared_ptr是否会被释放掉;使用lock来检测

  • 不控制对象生存期;指向一个shared_ptr管理的对象

  • 初始化用shared_ptr来初始化;wp不会改变p的引用计数,wp指向的对象可能被释放掉
C++
auto p = make_shared<int>(42);
weak_ptr<int> wp(p);
  • 不能使用weak_ptr直接访问对象;调用lock
  • lock函数检查weak_ptr指向的对象是否存在

设计优势

  • 方便判断某一时刻shared_ptr中是否已经释放对象;避免多次释放

  • 解决shared_ptr环形引用问题

  • 解决这种状况的办法就是将两个类中的一个成员变量改为weak_ptr对象,因为weak_ptr不会增加引用计数,使得引用形不成环,最后就可以正常的释放内部的对象,不会造成内存泄漏,比如将CB中的成员变量改为weak_ptr

C++
#include <memory>
#include <iostream>

class B;
class A {
public:
    ~A() {
        std::cout << "~A" << std::endl;
    }

    void setPtr(std::shared_ptr<B>& b) {
        bPtr_ = b;
    }

private:
    std::shared_ptr<B> bPtr_;
};

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

    void setPtr(std::shared_ptr<A>& a) {
        aPtr_ = a;
    }

private:
    std::shared_ptr<A> aPtr_;
};

// 解决方案
// 将B中的智能指针换成weak_ptr
int main() {
    std::shared_ptr<A> aShared = std::make_shared<A>();
    std::shared_ptr<B> bShared = std::make_shared<B>();

    aShared->setPtr(bShared);
    bShared->setPtr(aShared);

    return 0;
}

动态数组

  • 动态分配一个空数组是合法的
  • 得到的是一个指针;不能使用begin()或end()
C++
size_t n = get_size();
int* p = new int[n];
for(int * q = p; q != p + n; ++q)
    /* 处理数组 */

智能指针和动态数组

  • 不能使用.或->,使用下标来访问
C++
unique_ptr<int[]> up(new int[10]);
up.release(); // 自动调用delete []
for(size_t i = 0; i != 10; ++i)
    up[i] = i;
  • shared_ptr不直接支持动态数组管理
  • 必须自己定义删除器来操作
C++
shared_ptr<int> sp(new int[10], [](int *p){delete[] p;});   // 定义删除器
for(size_t i = 0; i != 10; ++i)
    *(sp.get() + i) = i;    // 使用get获取一个内置指针

allocator类

  • 分配的内存是原始的,未构造的
  • 根据给定的对象类型来确定恰当的内存大小和对齐位置
C++
allocator<string> alloc;
auto const p = alloc.allocate(n);

C++
auto q = p; // q指向最后构造的元素之后的位置
alloc.construct(q++);           // *q is ""
alloc.construct(q++, 2, 'c');   // *q is "cc"
alloc.construct(q++, "hi!");    // *q is "hi!"
  • 使用allocate返回的内存,必须用constuct构造对象;使用未构造的内存,行为是未定义的
C++
while(q != p)
    alloc.destroy(--q);
  • 只能对真正构造的元素进行destroy操作

拷贝和填充未初始化内存算法

C++
auto p = alloc.allocate(vi.size() * 2);
auto q = uninitialized_copy(vi.begin(), vi.end(), p);
auto q = uninitialized_fill_n(q, vi.size(), 42);

源码

unique_ptr

  • 有两个参数,第一个参数是指针所指的类型;第二个参数是删除器,规定指针释放时的操作
  • 禁止左值赋值和拷贝
  • 定义的变量类型以及函数应该在__uniq_ptr_impl类中
C++
// 成员变量
__uniq_ptr_impl<_Tp, _Dp> _M_t;

// Disable copy from lvalue.
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
// 变量类型
using pointer = typename __uniq_ptr_impl<_Tp,_Dp>::pointer;
using element_type  = _Tp;
using deleter_type  = _Dp;
// 成员函数
/// Return the stored pointer.
pointer get() const noexcept
{ return _M_t._M_ptr(); }

/// Return a reference to the stored deleter.
deleter_type& get_deleter() noexcept
{ return _M_t._M_deleter(); }

/// Return a reference to the stored deleter.
const deleter_type& get_deleter() const noexcept
{ return _M_t._M_deleter(); }

/// Return @c true if the stored pointer is not null.
explicit operator bool() const noexcept
{ return get() == pointer() ? false : true; }

/// Release ownership of any stored pointer.
pointer release() noexcept
{
    pointer __p = get();
    _M_t._M_ptr() = pointer();
    return __p;
}

class __uniq_ptr_impl

  • 存放指针和删除器的一个结构
C++
  template <typename _Tp, typename _Dp>
    class __uniq_ptr_impl
    {
      // 用来得到指针类型
      template <typename _Up, typename _Ep, typename = void>
    struct _Ptr
    {
      using type = _Up*;
    };
    public:
      // Constructor
      // 获取tuple中的元素
      pointer&   _M_ptr() { return std::get<0>(_M_t); }
      pointer    _M_ptr() const { return std::get<0>(_M_t); }
      _Dp&       _M_deleter() { return std::get<1>(_M_t); }
      const _Dp& _M_deleter() const { return std::get<1>(_M_t); }
    private:
      tuple<pointer, _Dp> _M_t;
    };

shared_ptr

  • 构造函数调用的__shared_ptr的构造函数
  • 该类没有重载*->运算符,从这点看shared_ptr似乎无法实现普通指针的功能
  • 该类没有析构函数
  • 只有一个友元类weak_ptr
C++
 class shared_ptr : public __shared_ptr<_Tp>

__shared_ptr&__shared_ptr_access

  • 有两个类成员:_M_ptr(由智能指针接管的普通指针)、_M_refcount(引用计数器,类型为__shared_count)
    从构造函数看,_M_ptr获得了接管的普通指针的值,而_M_refcount的构造也同样需要这个值
  • 重载了*和->运算符,由shared_ptr继承使用,使得智能指针最终能拥有和普通指针一样行为,尽管智能指针本质上是一个对象
    从析构函数来看,里面啥也没做,说明接管的普通指针也不是在这里释放的,所以有可能是由_M_refcount来完成释放内存这个工作,下面分析__shared_count的实现
  • 将weak_ptr作为友元
C++
 // Define operator-> for shared_ptr<cv void>.
 // __shared_ptr_access<_Tp, _Lp, false, true>
      using element_type = _Tp;

      element_type*
      operator->() const noexcept
      {
    auto __ptr = static_cast<const __shared_ptr<_Tp, _Lp>*>(this)->get();
    _GLIBCXX_DEBUG_PEDASSERT(__ptr != nullptr);
    return __ptr;
      }

// __shared_ptr
element_type*      _M_ptr;         // Contained pointer.
__shared_count<_Lp>  _M_refcount;    // Reference counter.

__shared_count

  • __Sp_counted_ptr中继承__M_dispose函数和__M_destroy函数
  • __shared_count中的_Sp_counted_base<_Lp>* _M_pi成员由__Sp_counted_ptr来初始化
C++
// __Sp_counted_ptr
// user_count = 0;删除指针以及整个空间
virtual void
_M_dispose() noexcept
{ delete _M_ptr; }
// weak_count = 0;释放__Sp_counted_ptr对象
virtual void
_M_destroy() noexcept
{ delete this; }

// __shared_count
_M_pi = new _Sp_counted_ptr<_Ptr, _Lp>(__p);

__Sp_counted_base&&__Sp_counted_ptr

  • 实现计数器的增减
  • 提供了释放指针和释放计数器对象的虚函数
C++
// 实现计数器的增减和对象的销毁(user_count)
// user_count and weak_count

      // Called when _M_use_count drops to zero, to release the resources
      // managed by *this.
      virtual void
      _M_dispose() noexcept = 0;

      // Called when _M_weak_count drops to zero.
      virtual void
      _M_destroy() noexcept
      { delete this; }

  template<>
    inline void
    _Sp_counted_base<_S_single>::_M_add_ref_copy()
    { ++_M_use_count; }

  template<>
    inline void
    _Sp_counted_base<_S_single>::_M_release() noexcept
    {
      if (--_M_use_count == 0)
        {
          _M_dispose();
          if (--_M_weak_count == 0)
            _M_destroy();
        }
    }

  template<>
    inline void
    _Sp_counted_base<_S_single>::_M_weak_add_ref() noexcept
    { ++_M_weak_count; }

  template<>
    inline void
    _Sp_counted_base<_S_single>::_M_weak_release() noexcept
    {
      if (--_M_weak_count == 0)
        _M_destroy();
    }

  template<>
    inline long
    _Sp_counted_base<_S_single>::_M_get_use_count() const noexcept
    { return _M_use_count; }

weak_ptr

  • 有一个__weak_count和一个指针
C++
// 成员
element_type*    _M_ptr;         // Contained pointer.
__weak_count<_Lp>  _M_refcount;    // Reference counter.

__weak_count

  • 与shared_count相似,继承父类函数进行引用计数的加减
C++
_Sp_counted_base<_Lp>*  _M_pi;

__enable_shared_from_this

  • 将weak_ptr转化为shared_ptr
C++
__shared_ptr<_Tp, _Lp>
shared_from_this()
{ return __shared_ptr<_Tp, _Lp>(this->_M_weak_this); }

__shared_ptr<const _Tp, _Lp>
shared_from_this() const
{ return __shared_ptr<const _Tp, _Lp>(this->_M_weak_this); }

friend const __enable_shared_from_this*
__enable_shared_from_this_base(const __shared_count<_Lp>&,
const __enable_shared_from_this* __p)
{ return __p; }

// 成员变量
mutable __weak_ptr<_Tp, _Lp>  _M_weak_this;

enable_shared_from_this

C++
shared_ptr<_Tp>
shared_from_this()
{ return shared_ptr<_Tp>(this->_M_weak_this); }

shared_ptr<const _Tp>
shared_from_this() const
{ return shared_ptr<const _Tp>(this->_M_weak_this); }

friend const __enable_shared_from_this*
__enable_shared_from_this_base(const __shared_count<_Lp>&,
const __enable_shared_from_this* __p)
{ return __p; }

// 成员变量
mutable __weak_ptr<_Tp, _Lp>  _M_weak_this;

delete this

  • 释放管理的对象

注意事项

  • 你必须100%绝对确保”this”对象是通过new分配的(不是通过new[],不是placement new,不是栈上的局部对象,不是全局对象,不是另一个对象的数据成员;仅仅只是通过原始的new运算符)
  • 你必须100%绝对确保调用”delete this”操作的成员函数是最后调用的成员函数
  • 你必须100%绝对确保在当前函数中”delete this”后,调用的其他成员函数不会读取”this”对象。
  • 你必须100%确保再也不会使用”this”指针。即使你使用this指针和其他指针比较,例如nullptr,打印this指针,转换this指针等等。