Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is there no safe alternative to unique_ptr::operator*()?

std::vector has the member function at() as a safe alternative to operator[], so that bound checking is applied and no dangling references are created:

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().
}

However, std::unique_ptr lacks the corresponding functionality:

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

It would be great, if std::unique_ptr had such a safe alternative, say member ref() (and cref()) which never returns a dangling reference, but rather throws an exception. Possible implementation:

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*();
}

Is there any good reason why the standard doesn't provide this sort of thing?

like image 774
Walter Avatar asked Aug 18 '15 08:08

Walter


3 Answers

unique_ptr was specifically designed as a lightweight pointer class with null-state detection (e.g. stated in optional in A proposal to add a utility class to represent optional objects (Revision 3))

That said, the capability you're asking is already in-place since operator* documentation states:

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

The pointer type is defined as

std::remove_reference<Deleter>::type::pointer if that type exists, otherwise T*

Therefore through your custom deleter you're able to perform any on-the-fly operation including null pointer checking and exception throwing

#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;
    }

}

Live Example

For completeness' sake I'll post two additional alternatives/workarounds:

  1. Pointer checking for a unique_ptr via 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';
    }
    

    (This would probably be the clanest way to deal with the issue)

  2. An alternative solution, although messier, is to use a wrapper type which takes care of the exception handling

like image 156
Marco A. Avatar answered Nov 11 '22 01:11

Marco A.


I suspect the real answer is simple, and the same one for lots of "Why isn't C++ like this?" questions:

No-one proposed it.

std::vector and std::unique_ptr are not designed by the same people, at the same time, and are not used in the same way, so don't necessarily follow the same design principles.

like image 36
Jonathan Wakely Avatar answered Nov 10 '22 23:11

Jonathan Wakely


I can't say, why the committee decided not to add a safe dereferenciation method - the answer is probably "because it wasn't proposed" or "because a raw pointer hasn't one either". But it is trivial to write a free function template on your own that takes any pointer as an argument, compares it against nullptr and then either throws an excepion or returns a reference to the pointed to object.

If you don't delete it via a pointer to base class, it should be even possible to derive publicly from a unique_ptr and just add such a member function.

Keep in mind however that using such a checked method everywhere might incur a significant performance hit (same as at). Usualy you want to validate your parameters at most once, for which a single if statement at the beginning is much better suited.

There is also the school that says you should not throw exceptions in response to programming errors. Maybe the peopke in charge of designing unique_ptr belonged to this school, while the people designing vector(which is much much older) didn't.

like image 4
MikeMB Avatar answered Nov 11 '22 00:11

MikeMB