7

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.

alter_igel
  • 6,899
  • 3
  • 21
  • 40
  • https://stackoverflow.com/questions/8147027/how-do-i-call-stdmake-shared-on-a-class-with-only-protected-or-private-const – VLL Sep 13 '18 at 05:58
  • 1
    Possible duplicate of [How do I call ::std::make\_shared on a class with only protected or private constructors?](https://stackoverflow.com/questions/8147027/how-do-i-call-stdmake-shared-on-a-class-with-only-protected-or-private-const) – lubgr Sep 13 '18 at 06:47
  • 1
    @lubgr he's not using `std::make_shared`, so the answers in the duplicate don't apply. – Alan Birtles Sep 13 '18 at 08:14
  • @AlanBirtles That's right, I retracted the close vote. – lubgr Sep 13 '18 at 08:38

3 Answers3

12

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.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
5

The cryptic visual studio error is caused by the shared_ptr constructor which takes just a pointer being disabled for un-deleteable types:

template<class _Ux,

    enable_if_t<conjunction_v<conditional_t<is_array_v<_Ty>, _Can_array_delete<_Ux>, _Can_scalar_delete<_Ux>>,

        _SP_convertible<_Ux, _Ty>>, int> = 0>

    explicit shared_ptr(_Ux * _Px)

The constructor is disabled as c++17 states:

This constructor additionally does not participate in overload resolution if the delete expression is not well-formed.

This leaves the various copy and move constructors as the only single argument constructors, the compiler chooses the move constructor as the closest match but can't convert a raw pointer to shared_ptr<T>&&, the nullptr_t part of the error message is because the compiler is trying to use that as an intermediate step as it is convertible to shared_ptr<T>&&.

You need to provide a custom deleter to workaround this problem:

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

As noted by others your objects will never be deleted as they contain a strong reference to themselves. You should either change m_sharedthis to a std::weak_ptr or use std::shared_from_this.

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
0

You disabled "anyone but class A" to create and destroy objects of class A. That means that a temporal object of class A cannot be created anywhere else.

It's likely that temporal object creation you "broke" SFINAE pattern of template.

shared_ptr constructor accepts allocator and deleter that can encapsulate some legal ways to delete your object

Swift - Friday Pie
  • 12,777
  • 2
  • 19
  • 42