4

I'd want to implement a builder pattern in modern C++. Coming from Java background, this is something I'd like to emulate:

// Usage
FooBuilder builder;
builder.setArg1(a);
builder.setArg2(b);
Foo foo = builder.build();

// Implementation
public class FooBuilder {
    // ...
    public Foo build() {
        return new Foo(a, b);
    }
}

Typical older textbooks just advice one to do it like that in C++:

class FooBuilder {
    // ...
    Foo* build() {
        return new Foo(m_a, m_b);
    }
}

which is obviously not a good idea, as dealing with raw pointers might be error-prone. The best I've came up with so far is using std::unique_ptr manually:

class FooBuilder {
    // ...
    std::unique_ptr<Foo> build() {
        return std::make_unique<Foo>(m_a, m_b);
    }
}

// Usage
auto fooPtr = builder.build();
Foo& foo = *fooPtr;
foo.someMethod();

It's better, as it doesn't require manual delete, this two-liner conversion to reference is ugly, and, what's more important, it uses heap allocation, while simple builder-less version would be totally ok with just a simple stack allocation:

Foo foo(..., ...); // <= on stack

Are there any better ways to do that, i.e. without unique_ptr, or with some sort of on-stack allocation for Foo?

GreyCat
  • 16,622
  • 18
  • 74
  • 112

2 Answers2

5

There's no reason why you have to allocate on the heap to use the builder pattern. Just have your build() method return Foo directly:

class FooBuilder {
public:
    Foo build() { // You may consider having a &&-qualified overload
        return Foo{ ..., ... };
    }
};
Justin
  • 24,288
  • 12
  • 92
  • 142
4

Generally, if Foo is copy_constructible, then you can just return Foo by value.

#include <type_traits>

class Foo
{
    int i;

public:
    Foo(int i): i(i){}

};

static_assert(std::is_copy_constructible<Foo>::value, "Foo is copy-constructible");

struct FooFactory
{
    //...
    Foo build() {return Foo(1);}
};


int main()
{
    FooFactory factory;
    //...
    Foo foo = factory.build();
}

And new in c++17, is guaranteed copy elision, which means that you can return by value even if the type does not have copy or move constructors:

#include <type_traits>

class Foo
{
    int i;

public:
    Foo(int i): i(i){}
    // regular copy constructors don't exist for whatever reason. 
    Foo() = delete;
    Foo(Foo const& ) =delete;
    Foo(Foo&& ) = delete;
    Foo& operator=(Foo const&) = delete;
    Foo& operator=(Foo&& ) = delete;
};

static_assert(not std::is_copy_constructible<Foo>::value, "Foo is definitely not copy-constructible");


struct FooFactory
{
    //...
    Foo build() {return Foo(1);}
};


int main()
{
    FooFactory factory;
    //...
    Foo foo = factory.build();
}
Paul Belanger
  • 2,354
  • 14
  • 23
  • 4
    "*And new in c++17, is copy elision*" - copy elision is not new in C++17, it is a common optimization used by many compilers of earlier C++ versions. What *is* new in C++17 is **guaranteed** copy elision, whereas it is **optional** in earlier versions. – Remy Lebeau Jan 30 '18 at 20:51
  • 1
    Prior to C++17 the compiler must still complain if the copy constructor is not available, even if it is able to perform copy elision. Now, because of the guaranteed copy elision, the copy construtor does not need to be present. But thank you for the correction, I've updated the post to insert that keyword. – Paul Belanger Jan 31 '18 at 13:38