Search code examples
c++c++14inner-classesbuilder

How to access private Constructor from within inner class? C++14


I am trying to apply the builder pattern to an object, but the private constructor is not visible from the inner class.

#include <iostream>
#include <memory>

class Outer{
private:
    Outer(void){ std::cout << "Constructed!" << std::endl; }
public:
    class Builder{
    public:
        std::unique_ptr<Outer> build(void){
            return std::make_unique<Outer>();
        }
    };
};

int main(int argc, char** agrs){
    std::unique_ptr<Outer> instance = Outer::Builder().build();
    return 0;
}

fails with the following error:


In file included from /usr/include/c++/8/memory:80,
                 from scrap.cpp:2:
/usr/include/c++/8/bits/unique_ptr.h: In instantiation of ‘typename std::_MakeUniq<_Tp>::__single_object std::make_unique(_Args&& ...) [with _Tp = Outer; _Args = {}; typename std::_MakeUniq<_Tp>::__single_object = std::unique_ptr<Outer>]’:
scrap.cpp:11:35:   required from here
/usr/include/c++/8/bits/unique_ptr.h:831:30: error: ‘Outer::Outer()’ is private within this context
     { return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...)); }
                              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
scrap.cpp:6:2: note: declared private here
  Outer(void){ std::cout << "Constructed!" << std::endl; }
  ^~~~~

I tried friend class Outer::Builder in the definition, but Outer is incomplete at the friend clause, so I couldn't make it work with that. I would very much like to restrict instantiation of the object to the Builder class, is there any way to do that in C++? Or is making the Outer constructor public the only option?


Solution

  • This doesn't work because the real builder is std::make_unique, and it is neither a friend nor a member. Making it a friend is not really possible, because you don't know what internal function it delegates to, and it would defeat the purpose of a private constructor anyway.

    You can just use bare new instead of std::make_unique, it will work in a pinch. If instead of a unique pointer you want a shared pointer, this becomes a bit more problematic, since the performance will not be as good.

    Here's how to make it work for unique_ptr, shared_ptr or any other kind of handle.

    #include <memory>
    
    class Outer
    {
        private:
            Outer();
        public:
            class Builder
            {
                private:
                    class InnerOuter;
                public:
                    std::unique_ptr<Outer> build();
            };
    };
    
    class Outer::Builder::InnerOuter : public Outer
    {
        public:
            using Outer::Outer;
    };
    
    std::unique_ptr<Outer> Outer::Builder::build()
    {
        return std::make_unique<InnerOuter>();
    }
    

    Now only Outer::Builder can refer to (and construct) an InnerOuter, because it is a private class. But its constructor is public, so std::make_unique can access it.

    Note, InnerOuter can access a private constructor of Outer because it is a member of a member of Outer and have member access to Outer.