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

作者: , 共 2093 字 , 共阅读 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++, C++标准库
std::tuple的原理并不复杂,但有些细节非常有意思。其中有一个是至少在gnu C++ std的实现中,std::tuple是倒序存储的:
编程 » C++, 智能指针
前面已经提到std::shared_ptr有三个缺陷:
编程 » 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++, 编译错误
在 gcc 中,存在继承关系的模版类,子类无法直接访问父类的成员,即使该成员是protectedpublic
编程 » C++, Boost, 数据容器
Boost.Intrusive 是一个很有意思的实现,里面实现了很多侵入式容器,在特定环境下,可以大大提升性能。
编程 » C++,
C++的多行宏有标准定义方式,boostfolly库都采用了这种方式:
编程 » C++, folly
folly::fbstring是一个完全兼容std::string的类,可以做到无缝替换,而且性能更高。其代码参见https://github.com/facebook/folly/blob/master/folly/FBString.h
编程 » C++, C++标准库
std::tuple的原理并不复杂,但有些细节非常有意思。其中有一个是至少在gnu C++ std的实现中,std::tuple是倒序存储的: