17

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?

Palec
  • 12,743
  • 8
  • 69
  • 138
Walter
  • 44,150
  • 20
  • 113
  • 196
  • It's easy to test for out-of-bounds (the bounds being part of the object on which the method/operator is invoked). How would it work for unique_ptr? – user2864740 Aug 18 '15 at 08:27
  • @user2864740 OP wants to check for emptiness of a pointer, not bounds – Piotr Skotnicki Aug 18 '15 at 08:28
  • 4
    The title is somewhat confusing, my first idea was that the question is about bounds checking for something lik `std::unique_ptr pFoo(new int[n]);` which can't be done. – Rudolfs Bundulis Aug 18 '15 at 08:30
  • 4
    "*creates dangling reference if x.empty()*". Actually, the behavior is explicitly [*undefined*](http://www.cplusplus.com/reference/vector/vector/operator%5B%5D/), which is the same as for `*(unique_ptr(NULL))`, albeit the latter will most likely segfault. – dhke Aug 18 '15 at 08:36
  • @dhke you're right. I added that fact in the comment of the code example. – Walter Aug 18 '15 at 08:41
  • I'll post this as a comment since it doesn't address your question fully: a possible workaround http://ideone.com/f4r8Gs – Marco A. Aug 18 '15 at 08:42
  • 1
    Still, I'd actually like to know an answer, too. More generally, *why* the "managed" C++ pointer types don't come with an `std::undefined_pointer_exception` or similar. Since you can check for the validity of the pointers manually, the omission of the exception actually seems odd. – dhke Aug 18 '15 at 08:42
  • 2
    I suspect that it's because `nullptr` is the only invalid pointer you can (portably) test for, so the function isn't very safe, or even useful, at all. – molbdnilo Aug 18 '15 at 08:43
  • @dhke we've got assertions for that. – Quentin Aug 18 '15 at 08:44
  • 1
    @Quentin That's not the same. Of course I can `assert(x)` to check the pointer, but I cannot *catch the assertion*. `std::unique_ptr` has a well defined *invalid* state that is not exposed to try/catch error handling. Of course it's not required, but it also another sinkhole down to undefined behavior when it would actually be possible to have defined behavior (even if you explicitly need to request it). So `unique_ptr::get()` (opposite to `operator*()`) could actually throw for invalid pointers. It doesn't. – dhke Aug 18 '15 at 08:51
  • 2
    @dhke In fact, I agree that the reasoning behind `vector.at()` would be the same as the one behind an `unique_ptr.at()`, but what I wonder is *why `vector.at()` exists*. When you dereference a null pointer or access outside of an array... Then what ? You were going to use an object, but there's no object there. That's not an exceptional condition, but an invariant failure. Plus, if you want a meaningful exception, you need to intercept the null dereference, and throw your own exception from context. And `if(!p) throw ...;` looks better than `try {...} catch(...) { throw ...; }`. – Quentin Aug 18 '15 at 08:58
  • 1
    I have to agree with Quentin here. You want this functionality so you can avoid an `if()` statement and instead have a `try...catch`? Honestly, I didn't know that `vector.at()` was a function for a long time - I just checked the bounds of the vector before accessing it. But then again, I tend to use the "check preconditions" model rather than having exception handling everywhere (not saying either way is better). – JPhi1618 Aug 18 '15 at 13:05
  • 1
    As the inventor of `unique_ptr` I can say that there are lots of good answers here, and several good suggestions on how to get the behavior you want. And of those, none of them are demonstrably better than any of the rest. If one answer were to be standardized, someone would ask: "How come it wasn't done the other way?" And before you know it, the `unique_ptr` API would look a lot like the `string` API: bloated. The current API for `unique_ptr` is sufficient for you to do whatever you want to. No further help from the standard is needed. – Howard Hinnant Aug 18 '15 at 22:59
  • @HowardHinnant The argument *No further help from the standard is needed* could (should) have been applied to obtain a leaner standard library API. So, it certainly is/was not a driving motivation in the design of the standard library. My question boils down to *Why is the standard library API inconsistent regarding this (why has `vector` an `at()` but `unique_ptr` no `ref()`)?* – Walter Aug 19 '15 at 08:00
  • @Walter: Jonathan and MikeMB correctly addressed that point (and perhaps others, I am skimming). The standard library is a melting pot of volunteers. There is no entity overseeing the project with the power to hire and fire. – Howard Hinnant Aug 19 '15 at 13:25
  • @HowardHinnant That sounds like a poorly run project. I would have hoped that the design and implementation of the standard library is guided by some overarching principles, that would avoid such inconsistencies. – Walter Aug 20 '15 at 07:32
  • @Walter: You're welcome to volunteer your services to improve the project. Stop by any time. I'm sure we could benefit from your wisdom. – Howard Hinnant Aug 20 '15 at 13:59

6 Answers6

17

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

Marco A.
  • 43,032
  • 26
  • 132
  • 246
  • 1
    That's exactly not the type of thing I asked for. I want to be able to *choose* between `operator*` and a safe alternative, exactly as with `vector::operator[]` and `vector::at()`. The latter only used in places where the extra costs are tolerable. – Walter Aug 18 '15 at 10:52
  • 3
    If you *want* it, submit a proposal to the committee and good luck. I see no other way. – Marco A. Aug 18 '15 at 10:53
  • 1
    @Walter You can get access to the underlying pointer with `get()` (part of the `std::unique_ptr` interface) and choose which form of access you want there. Since the `pointer` hook of `std::unique_ptr` is exactly designed for this sort of business, this answer is absolutely appropriate. – Luc Danton Aug 18 '15 at 11:02
  • @LucDanton: `get` also returns `pointer`, so would also be subject for the test. – Matthieu M. Aug 18 '15 at 11:37
  • @MatthieuM. That’s not how I’m reading the spec. Which test? It can’t be the one in `InternalPtr` since we’re returning that. – Luc Danton Aug 18 '15 at 11:43
  • @LucDanton: Ah, sorry; I thought you mean that `get()` would return a `T*` directly. Indeed `gets` returns an `InternalPtr`, so then you can unchecked accesses. – Matthieu M. Aug 18 '15 at 11:50
  • I should have said `pointer` instead of the ambiguous 'underlying pointer'. – Luc Danton Aug 18 '15 at 12:05
13

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.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • I agree, or more because: it's how the standard is written. There was some discussion about it, relevant answer: http://stackoverflow.com/questions/15203541/why-does-stdunique-ptr-operator-throw-and-operator-does-not-throw – dau_sama Aug 18 '15 at 09:06
  • 2
    @dau_sama `unique_ptr::operator*()` only throws if/because the pointer throws, not by its own. – Walter Aug 18 '15 at 10:38
4

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.

MikeMB
  • 20,029
  • 9
  • 57
  • 102
  • 1
    I don't think inheriting from `unique_ptr` is a wise idea (it may even be illegal), but I like the free function template. Ideally it has a `static_assert` to avoid it being called with anything but an ordinary pointer or a `unique_ptr`. Could you provide an implementation in your answer? – Walter Aug 18 '15 at 08:57
  • @Walter: But I agree, that it wouldn't be wise to derive from unique_ptr in any case. – MikeMB Aug 18 '15 at 09:33
  • 1
    @Walter, it's not illegal. – Jonathan Wakely Aug 18 '15 at 10:38
3

One of the main goals of a smart pointer API design is to be a drop-in replacement with added value, no gotchas or side effects, and close to zero overhead. if (ptr) ptr->... is how safe access to bare pointer is usually done, the same syntax works nicely with smart pointers thus requiring no code change when one is replaced with the other.

An additional check for validity (say, to throw an exception) put inside a pointer would interfere with branch predictor and thus may have a knock-on effect on the performance, which may not be considered a zero cost drop-in replacement anymore.

bobah
  • 18,364
  • 2
  • 37
  • 70
  • 3
    okay, that's an argument, but the branching applies equally well to `vector::at()`. One would only use `unique_ptr::ref()` if one knows that one deals with a `unique_ptr`, not when dealing with an opaque pointer-like object -- in this latter case one could indeed simply test `bool(obj)`. – Walter Aug 18 '15 at 08:47
  • 9
    How would a completely separate method have any effect on `unique_ptr` being a drop in replacement for a raw pointer? Having extra methods on a class, especially a template class, doesn't generally add any overhead at all. – Benjamin Lindley Aug 18 '15 at 09:26
  • @BenjaminLindley - I'd explain it by "separation of concerns". It's a pointer - a lowest level language building block. Making it feature rich will cause more harm than good. Plus wrapping your favorite "smart_ptr" into things like "safe_ptr" "lazy_ptr" etc. is trivial, but extending the `std::vector` is probably not. – bobah Aug 18 '15 at 09:38
  • a `unique_ptr` cannot be a *drop-in replacement* for a pointer. You can declare a class member of type pointer to object that has been forward-declared (but not defined). You cannot do that easily with a `unique_ptr`, its destructor must call the destructor of the pointed-to object (you can avoid this issue with a custom deleter, but that is not what I call *drop-in replacement*). – Walter Aug 18 '15 at 19:49
  • @Walter - when implementing pimpl on `std::unique_ptr` you just declare enclosing class constructor in the header and define it in the compilation unit, possibly with `= default;`, not with custom deleter. Otherwise the trick with deleter shown in the MarcoA's answer settles the argument - the feature is already there, delegated to the deleter. – bobah Aug 18 '15 at 20:31
2

You do have

operator bool()

Example from: cplusplusreference

// example of unique_ptr::operator bool
#include <iostream>
#include <memory>


int main () {
  std::unique_ptr<int> foo;
  std::unique_ptr<int> bar (new int(12));

  if (foo) std::cout << "foo points to " << *foo << '\n';
  else std::cout << "foo is empty\n";

  if (bar) std::cout << "bar points to " << *bar << '\n';
  else std::cout << "bar is empty\n";

  return 0;
}

unique_ptr is a simple wrapper to a raw pointer, no need to throw an exception when you can just check a boolean condition easily.

Edit: Apparently operator* can throw.

Exceptions 1) may throw, e.g. if pointer defines a throwing operator*

Maybe someone could shed some lights on hot to define a throwing operator*

dau_sama
  • 4,247
  • 2
  • 23
  • 30
  • I knew all that, but that doesn't answer the question. – Walter Aug 18 '15 at 08:38
  • 2
    question is: why would you another method that adds the extra cost of handling exceptions when you can simply check the validity of the pointer with the more simple boolean operator? I can't find discussions on the committee when this was introduced, but I imagine this was the rationale behind. – dau_sama Aug 18 '15 at 08:40
  • 3
    The `n < v.size()` check for `std::vector` isn't much more cumbersome, yet we have `std::vector::at()`. – alcedine Aug 18 '15 at 08:41
  • another point: imagine you could not declare any functions where you would use this as noexcept. I you think it makes really sense, I suggest you to submit a proposal to the standard commitee: https://isocpp.org/std/submit-a-proposal I, myself have never found the need for such a method, but maybe they will see it differently. – dau_sama Aug 18 '15 at 08:46
  • @dau_sama [take a look at this](http://stackoverflow.com/a/32067541/1938163) for the `operator*` exception throwing – Marco A. Aug 18 '15 at 09:58
2

Following from the suggestion of MikeMB, here is a possible implementation of a free function for dereferencing pointers and unique_ptrs alike.

template<typename T>
inline T& dereference(T* ptr) noexcept(false)
{
  if(!ptr) throw std::runtime_error("attempt to dereference a nullptr");
  return *ptr;
}

template<typename T>
inline T& dereference(std::unique_ptr<T> const& ptr) noexcept(false)
{
  if(!ptr) throw std::runtime_error("attempt to dereference an empty unique_ptr)");
  return *ptr;
}
Walter
  • 44,150
  • 20
  • 113
  • 196