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?
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:
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)
An alternative solution, although messier, is to use a wrapper type which takes care of the exception handling
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With