-1

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?

Dávid Tóth
  • 2,788
  • 1
  • 21
  • 46
  • 1
    The problem is that `std::make_unuque` needs to access your private constructor, and it cannot, because it is neither a friend nor a member. – n. m. could be an AI Jun 10 '21 at 09:44
  • https://stackoverflow.com/a/5013735/1467600 says otherwise, but you are right with c++14.. ( in c++14 there is the same error as pre c++11 ) – Dávid Tóth Jun 10 '21 at 09:56
  • In C++03 you would need to make the inner class a friend to refer to the private ctor, but your inner class does not rever to the ctor anyway. `std::make_unique` does. – n. m. could be an AI Jun 10 '21 at 10:08
  • 1
    @DavidTóth you missed the point what `n. 1.8e9-where's-my-share m.` wrote you. There is no problem accessing outside class from inner class or vice versa, it is problem that template `std::make_unique` can't access none public constructors. – Marek R Jun 10 '21 at 10:09
  • OH I missed that! Right... – Dávid Tóth Jun 10 '21 at 10:15
  • Does this answer your question? [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) – JaMiT Jun 10 '21 at 11:01
  • @JaMiT Not especially, because the focus of the problem was not on the usage of `std::make_unique`; and using `std::unique_ptr(new Outer)` is the simplest solution possible ( which is a whole different solution than the one you linked has ). Thank you for the suggestion anywway! – Dávid Tóth Jun 10 '21 at 11:08

3 Answers3

2

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.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
2

What about:

#include <iostream>
#include <memory>

class Outer{
private:
    Outer(void){ std::cout << "Constructed!" << std::endl; }
public:
    friend class std::unique_ptr<Outer> std::make_unique<Outer>();
    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;
}

You can make a friend function in C++ too.

xryl669
  • 3,376
  • 24
  • 47
  • Now you can say `std::unique_ptr instance2 = std::make_unique();` in `main`. What's the purpose of having a builder then? – n. m. could be an AI Jun 10 '21 at 10:10
  • In the example not much, but in the actual code usage, the constructor parameters differ, so this is something I would accept as an answer. – Dávid Tóth Jun 10 '21 at 10:16
  • 1
    @DavidTóth Accept what you want, but "constructor parameters differ" has nothing to do with my remark. If you have `std::make_unique` as a friend, you can bypass `Builder`, with any constructor parameters. Just make the constructor public then. – n. m. could be an AI Jun 10 '21 at 10:20
  • 1
    If `friend` is used I would do it by `friend template< class T, class... Args > unique_ptr std::make_unique( Args&&... args );`. – Marek R Jun 10 '21 at 10:20
  • @MarekR It doesn't compile with gcc: `friend template< class T, class... Args > unique_ptr std::make_unique( Args&&... args );`, the one I come up with is somewhat weird but compiles: `template friend typename std::_MakeUniq::__single_object std::make_unique(Args&&... args);` – prehistoricpenguin Jun 10 '21 at 10:37
  • you are right @n.1.8e9-where's-my-sharem. Your answer for with the new operator is the one I went with, exactly because of the privacy concerns. – Dávid Tóth Jun 10 '21 at 11:03
0

Depend on details which are not described in question, there are other ways to do solve the issue.

For example the simplest way if virtual calls are fine for you just use factory pattern. This will completely hide implementation details (this is common approach when using dependency injection) object can be constructed since Outer is completely abstract.

// header
#include <memory>

class Outer {
public:
    virtual ~Outer();

    virtual void fun1() = 0;
    virtual int fun2(int) = 0;

    class Builder{
    public:
        std::unique_ptr<Outer> build(void);
};
#include "Outer.h"

Outer::~Outer() = default;

class ImplOuter : public Outer {
public:

    void fun1() override {
        // todo
    }
    int fun2(int) override {
        // todo
        return 0;
    }
};

std::unique_ptr<Outer> Outer::Builder::build(void)
{
     return std::make_unique<ImplOuter>();
}
#include <iostream>
#include "Outer.h"

int main(int argc, char** agrs){
    auto instance = Outer::Builder().build();
    instance->fun1();
    return 0;
}
Marek R
  • 32,568
  • 6
  • 55
  • 140