为什么unique_ptr :: operator*()没有安全的替代方案?

Wal*_*ter 17 c++ unique-ptr dereference c++11 dangling-pointer

std::vector将成员函数at()作为安全替代operator[],以便应用绑定检查并且不会创建悬空引用:

void foo(std::vector<int> const&x)
{
  const auto&a=x[0];     // What if x.empty()? Undefined behavior!
  const auto&a=x.at(0);  // Throws exception if x.empty().
}
Run Code Online (Sandbox Code Playgroud)

但是,std::unique_ptr缺少相应的功能:

void foo(std::unique_ptr<int> const&x)
{
  const auto&a=*x;       // What if bool(x)==false? Undefined behavior!
}
Run Code Online (Sandbox Code Playgroud)

如果std::unique_ptr有这样一个安全的替代方案,那就好了,说成员ref()(和cref())永远不会返回一个悬空引用,而是抛出异常.可能的实施:

template<typename T>
typename add_lvalue_reference<T>::type
unique_ptr<T>::ref() const noexcept(false)
{
  if(bool(*this)==false)
    throw run_time_error("trying to de-refrence null unique_ptr");
  return this->operator*();
}
Run Code Online (Sandbox Code Playgroud)

这个标准没有提供这种东西有什么好的理由吗?

Mar*_* A. 17

unique_ptr是专门设计为具有空状态检测的轻量级指针类(例如,在提议中以可选方式声明,以添加实用程序类来表示可选对象(修订版3))

也就是说,由于操作员*文档声明,您所要求的功能已经就位:

// may throw, e.g. if pointer defines a throwing operator*
typename std::add_lvalue_reference<T>::type operator*() const;
Run Code Online (Sandbox Code Playgroud)

pointer类型被定义为

std::remove_reference<Deleter>::type::pointer if that type exists, otherwise T*
Run Code Online (Sandbox Code Playgroud)

因此,通过自定义删除器,您可以执行任何即时操作,包括空指针检查和异常抛出

#include <iostream>
#include <memory>

struct Foo { // object to manage
    Foo() { std::cout << "Foo ctor\n"; }
    Foo(const Foo&) { std::cout << "Foo copy ctor\n"; }
    Foo(Foo&&) { std::cout << "Foo move ctor\n"; }
    ~Foo() { std::cout << "~Foo dtor\n"; }
};

struct Exception {};

struct InternalPtr {
    Foo *ptr = nullptr;
    InternalPtr(Foo *p) : ptr(p) {}
    InternalPtr() = default;

    Foo& operator*() const {
        std::cout << "Checking for a null pointer.." << std::endl;
        if(ptr == nullptr)
            throw Exception();
        return *ptr;
    }

    bool operator != (Foo *p) {
        if(p != ptr)
            return false;
        else
            return true;
    }
    void cleanup() {
      if(ptr != nullptr)
        delete ptr;
    }
};

struct D { // deleter
    using pointer = InternalPtr;
    D() {};
    D(const D&) { std::cout << "D copy ctor\n"; }
    D(D&) { std::cout << "D non-const copy ctor\n";}
    D(D&&) { std::cout << "D move ctor \n"; }
    void operator()(InternalPtr& p) const {
        std::cout << "D is deleting a Foo\n";
        p.cleanup();
    };
};

int main()
{
    std::unique_ptr<Foo, D> up(nullptr, D()); // deleter is moved

    try {
      auto& e = *up;      
    } catch(Exception&) {
        std::cout << "null pointer exception detected" << std::endl;
    }

}
Run Code Online (Sandbox Code Playgroud)

Live Example

为了完整起见,我将发布两个额外的替代方案/解决方法:

  1. 指针检查unique_ptr通道operator bool

    #include <iostream>
    #include <memory>
    
    int main()
    {
        std::unique_ptr<int> ptr(new int(42));
    
        if (ptr) std::cout << "before reset, ptr is: " << *ptr << '\n';
        ptr.reset();
        if (ptr) std::cout << "after reset, ptr is: " << *ptr << '\n';
    }
    
    Run Code Online (Sandbox Code Playgroud)

    (这可能是解决问题的最重要方式)

  2. 另一种解决方案,虽然更麻烦,但是使用包装类型来处理异常处理

  • 如果您*需要*,请向委员会提交建议并祝您好运.我没有别的办法. (3认同)

Jon*_*ely 13

我怀疑真正的答案很简单,而且很多"为什么不是这样的C++?" 问题:

没有人提出它.

std::vector并且std::unique_ptr不是由同一个人设计的,同时也不是以相同的方式使用,因此不一定遵循相同的设计原则.

  • @dau_sama`unique_ptr :: operator*()`仅在/因为指针抛出时抛出,而不是由它自己抛出. (2认同)