Search code examples
c++shared-ptrfactorymake-sharedprivate-constructor

Passkey idiom with std::make_shared. xmemory cannot access private Key constructor


In my program I need a factory function that provides instances of separate class because I need control over the details of each instance and to be aware of how many instances are in existence at a time. In particular returning a std::shared_ptr is ideal, but this is initially impossible due to a known issue with the "make" fucntions of the std::pointer types as they would need to be friends with my Widget class as well, which isn't portable since it relies on the current implementation of those methods that may change.

To get around this, I want to employ the Passkey idiom, which was directly recommend for this situation as described at the bottom of this: https://abseil.io/tips/134. I also based my implementation off the lessons learned here: https://arne-mertz.de/2016/10/passkey-idiom/

This is a sample project that uses my same setup as my full project:

#include <iostream>

class Widget
{
public:
    class Key
    {
        friend class Factory;
    private:
        Key() {};
        Key(const Key&) = default;
    };

    int mTest;

    explicit Widget(Key, int test) { mTest = test; }

    int getTestVar() { return mTest; }
};

class Factory
{
public:

    int mTestPass;

    Factory(int input) { mTestPass = input; }

    std::shared_ptr<Widget> factoryMake() { return std::make_shared<Widget>(Widget::Key{}, mTestPass); }
};

int main()
{
    Factory testFactory(10);
    std::shared_ptr<Widget> testWidget = testFactory.factoryMake();

    std::cout << testWidget->getTestVar();

    return 0;
}

However, I get

Error   C2248   'Widget::Key::Key': cannot access private member declared in class 'Widget::Key'    TestProject ...\include\xmemory 204 

This has me completely lost, since the error coming from xmemory.cpp indicates that std::make_shared is sill trying to access a private constructor. As far as I'm aware, the construction of the Key instance occurs within the factoryMake() function, which belongs to Factory, and then that instance is passed into the std::make_shared function; therefore, std::make_shared should not need access to the Key constructor since an already constructed instance is being passed to it, which is the entire point of using this idiom in this context. The class itself is public so it should have no issues interacting with the type Key, only the constructor should be inaccessible.

In the end I can just skip using std::make_shared and instead use the shared_ptr(*T) constructor with a raw pointer, but this is slightly less efficient due to the extra allocation it requires, as noted in my first link. It isn't a big deal as I'm not making many widgets but I'd ultimately prefer to get the more ideal implementation working.

What am I missing here?


Solution

  • The problem is that the compiler needs to copy your Widget::Key when you call std::make_shared, and you have declared the copy constructor private. You can solve this in one of two ways:

    1. Make the copy constructor of Widget::Key public.

    2. Change the Widget constructor to take the Widget::Key by const reference:

      explicit Widget(const Key&, ...