Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does making operator new private break std::shared_ptr?

I'm implementing a polymorphic type (call it A) that I want to have managed exclusively via std::shared_ptr. To allow using shared_from_this in the constructor of derived classes, A is allocated using new and then initializes a member std::shared_ptr to itself to manage its lifetime automatically. To help enforce this, I've decided to make the class-specific operator new private, and plan to use a create() helper function instead of new and make_shared. The design might look a bit funny but it makes sense in context for the UI library I'm working on. A minimal reproducible example is as follows:

struct A {
    A() : m_sharedthis(this) {

    }

    void destroy(){
        m_sharedthis = nullptr;
    }

    std::shared_ptr<A> self() const {
        return m_sharedthis;
    }

private:
    friend std::shared_ptr<A> create();

    std::shared_ptr<A> m_sharedthis;
};

std::shared_ptr<A> create(){
    auto a = new A();
    return a->self();
}

This compiles and works fine. The problem arises when I add the following code to the body of A:

struct A {
    ...
private:
    void* operator new(size_t size){
        return ::operator new(size);
    }
    void operator delete(void* ptr){
        ::operator delete(ptr);
    }
    ...
};

Now this fails to compile on MSVC 2017 with a rather cryptic error message:

error C2664: 'std::shared_ptr<A>::shared_ptr(std::shared_ptr<A> &&) noexcept': cannot convert argument 1 from 'A *' to 'std::nullptr_t'
note: nullptr can only be converted to pointer or handle types

What's going on here? Why is the std::shared_ptr constructor trying to accept only nullptr suddenly?

EDIT: Yes, in the actual code, the class does derive from std::enable_shared_from_this, but it was not necessary to reproduce the error.

like image 920
alter_igel Avatar asked Sep 13 '18 05:09

alter_igel


1 Answers

When I tried your code, I got an entirely different error:

error C2248: 'A::operator delete': cannot access private member declared in class 'A'

And the culprit is this

m_sharedthis(this)

You provide a pointer, but no deleter. So std::shared_ptr will try to use the default deleter, which likely contains a delete expression on your type. Since that deleter is unrelated to your class, it cannot access the private operator delete.

A workaround is to supply a custom deleter

m_sharedthis(this, [](A* a) {delete a;})

The lambda, having been defined in the scope of the class, has access to operator delete at the point of its definition.

On an unrelated note. Your code as written is going to leak all those objects. Since the objects all hold a strong reference to themselves, how are they ever going to reach a reference count of zero? Consider using std:enable_shared_from_this instead.

like image 134
StoryTeller - Unslander Monica Avatar answered Oct 18 '22 05:10

StoryTeller - Unslander Monica