使用 std::unique_ptr 代替原始指针

作者: , 共 2088 字 , 共阅读 0

理论上而言,当 C++提供了std::unique_ptr, C++的程序就不应该出现普通指针了。所有普通指针都可以用std::unique_ptr代替,避免手动删除对象。

std::unique_ptr<int> x1 = new int{1};
std::unique_ptr<int[10]> x2 = new int[10]{2, 3, 4};

std::unique_ptr是一个标准的RAII实现,内部只维护了一个指针。因此它没有任何效率问题。

它的实现也特别简单直接。唯一的问题时,当删除原始资源时,需根据数据是单个数据还是数组,决定调用delete还是delete[]。这通过定义两个不同的Deleter来实现:

template<typename _Tp>
struct default_delete {
    void operator()(_Tp* __ptr) const {
        delete __ptr;
    }
};

template<typename _Tp>
struct default_delete<_Tp[]> {
    template<typename _Up>
    typename enable_if<is_convertible<_Up(*)[], _Tp(*)[]>::value>::type operator()(_Up* __ptr) const {
        static_assert(sizeof(_Tp)>0, "can't delete pointer to incomplete type");
        delete [] __ptr;
    }
};

然后再对两种情况分别定义unique_ptr,下面只列其中一个:

template <typename _Tp, typename _Dp = default_delete<_Tp>>
class unique_ptr {
    std::tuple<pointer, _Dp> _M_t;
public:
    ~unique_ptr() noexcept {
        auto& __ptr = _M_t.get<0>();
        if (__ptr != nullptr) {
            _M_t.get<1>(__ptr);
            _M_t.get<0>() = nullptr;
        }
    }
}

这里有一个很有意思的地方在std::tuple<pointer, _Dp>。一个很自然的想法是为什么不定义成下面这样,用起来更方便:

template <typename _Tp, typename _Dp = default_delete<_Tp>>
class unique_ptr {
    pointer _M_ptr;
    _Dp _M_dp;
}

其原因是默认的_Dp是一个空结构。如果按照上面这种写法,sizeof(unique_ptr)就变成了 16。而用std::tuple,那么sizeof(unique_ptr)只有 8。这个差异非常大。

为确定这一点,我们可以运行下面这段测试程序:

#include <iostream>
#include <memory>

struct Empty { };

struct A {
    double x;
    Empty e;
};

int main() {
  std::cout << sizeof(std::unique_ptr<int>) << std::endl;       // 8
  std::cout << sizeof(std::shared_ptr<int>) << std::endl;       // 16
  std::cout << sizeof(Empty) << std::endl;                      // 1
  std::cout << sizeof(A) << std::endl;                          // 16
  std::cout << sizeof(std::tuple<double, Empty>) << std::endl;  // 8
} 

其原因需要看std::tuple的具体实现

Q. E. D.

类似文章:
编程 » C++, 智能指针
前面已经提到std::shared_ptr有三个缺陷:
编程 » C++, C++标准库
std::tuple的原理并不复杂,但有些细节非常有意思。其中有一个是至少在gnu C++ std的实现中,std::tuple是倒序存储的:
编程 » C++, Boost, 智能指针
如果理解了侵入式容器,侵入式智能指针也很容易理解。传统的智能指针std::shared_ptr使用了和数据无关的引用计数,这带来两个问题:
智能指针在现代 C++里用得越多。以前只知道它大致的原理,比如使用引用计数。但很多实现细节并不清楚,最直接的,它是如何实现多线程安全的?今天找了 gnu c++ library 的实现好好看了一下。
编程 » C++, 异步
C++11 的标准异步库至少包含下面内容:
编程 » folly, C++, 数据容器
由 Facebook 开发和维护的 C++库 Folly 提供folly::small_vector,代码文件地址:https://github.com/facebook/folly/blob/master/folly/small_vector.h
由 Facebook 开发和维护的 C++库 Folly 提供了锁folly::MicroLock,代码文件地址:https://github.com/facebook/folly/blob/master/folly/MicroLock.h
C++对一个有序序列[first, last)firstlast都是iterator,可简单理解为位置指针),以及指定值v,标准库直接提供二分查找的函数std::lower_boundstd::upper_bould
编程 » C++, 编译错误
在 gcc 中,存在继承关系的模版类,子类无法直接访问父类的成员,即使该成员是protectedpublic
编程 » C++
在实现C++中非阻塞式的用户输入中发现,在没有设置in.sync_with_stdio(false)时,in.rdbuf()里面总是空的。
爬升约 500 米。景区内路线只有 5 到 6 公里,但若停车在公园 2 公里开外的大转盘,整体路线长度可到 9 到 10 公里,爬升也会增加约 50 米。
编程 » C++, C++标准库
std::tuple的原理并不复杂,但有些细节非常有意思。其中有一个是至少在gnu C++ std的实现中,std::tuple是倒序存储的: