Search code examples
c++c++11gccunique-ptr

Does std::unique_ptr support fancy pointers?


  • Can std::unique_ptr manage pointers only, or fancy pointers (pointer-like types) too?

I assume "fancy pointers too", because for resources like files/sockets, fancy pointers can be a great tool. And there seems no reason std::unique_ptr shouldn't support them.

  • Why are there inconsistent ways to test empty-ness of the managed pointer?

There are different ways to test empty-ness of the managed pointer in std::unique_ptr (GCC):

  • In ~unique_ptr(): if (__ptr != nullptr) (code)
  • In reset(): if (__old_p) (code)
  • In operator bool(): return get() == pointer() ? false : true; (code)

The problem is that they have different requirements to the pointer type:

  • bool operator ==(pointer, nullptr_t)
  • operator bool()
  • bool operator ==(pointer, pointer)

The inconsistent usage means all of them are needed, while one of them would suffice semantically.

#include <memory>

using namespace std;

class File {
    FILE * pf_ = nullptr;
public:
    File() = default;
    File(const char * pathname) { /*...*/ };
    void Close() { /*...*/ };

    // All are needed to make std::unique_ptr work
    explicit operator bool() const { return pf_ == nullptr; }
    bool operator ==(nullptr_t) const { return pf_ == nullptr; }
    bool operator ==(File) const { /*...*/ return true; };
};

struct FileCloser {
    using pointer = File;
    void operator ()(File p) const { p.Close(); }
};

int main() {
    using FilePtr = unique_ptr<File, FileCloser>;
    FilePtr p(File("./abc"));
    if(p)
        p.reset();
}

https://godbolt.org/z/fjPjWzzhW

IMHO, std::unique_ptr should require minimum from the pointer type. One solution is to add a static member, e.g. __test_p, and use it to test empty-ness consistently:

template<typename T, typename D = std::default_delete<T>>
class unique_ptr
{
// ...
private:
    static constexpr bool __test_p(pointer p) noexcept {
        if constexpr (is_convertible_v<pointer, bool>)
            return bool(p);
        else if constexpr (requires {{ p != nullptr} -> convertible_to<bool>; })
            return p != nullptr;
        else
            return p != pointer();
    }
// ...
};

Solution

  • Can std::unique_ptr manage pointers only, or fancy pointers (pointer-like types) too?

    Yes

    The requirements for std::unique_ptr<T, Deleter> are:

    • Deleter must be FunctionObject or lvalue reference to a FunctionObject or lvalue reference to function, callable with an argument of type unique_ptr<T, Deleter>::pointer.

    • std::remove_reference_t<Deleter>::pointer, if it exists, must satisfy NullablePointer.

    Among other things, a NullablePointer must satisfy all three of the requirements you claim are inconsistent, and they must all be consistent with each other.

    Why are there inconsistent ways to test empty-ness of the managed pointer?

    Having "redundant" operations is a trade off in favour of users of a constraint against implementors of a constraint, it is not "wrong".

    While it is true that operator bool must be equivalent to ptr != nullptr, and ptr == nullptr must be equivalent to ptr == pointer(nullptr), it's more ergonomic to have operator bool and all the comparisons. By ensuring that every NullablePointer can do every operation that raw pointers can, code can be written expecting raw pointers, and "just work" for fancy pointers.

    IMHO, std::unique_ptr should require minimum from the pointer type.

    That's your opinion. The committee chose otherwise.