Assuming there is a class Foo, which shouldn't be changed after construction, but it's difficult to pass everything via constructor, so we want some kind of builder for it. Something like this:
class Foo
{
public:
[[nodiscard]] std::vector<std::string> const& getParts() const
{
return parts;
}
private:
friend class ClassicFooBuilder;
std::vector<std::string> parts;
};
class ClassicFooBuilder
{
public:
void addPart(std::string name)
{
foo.parts.emplace_back(std::move(name));
}
[[nodiscard]] Foo const& result()
{
return foo;
}
private:
Foo foo;
};
Foo buildFoo()
{
ClassicFooBuilder builder;
builder.addPart("head");
builder.addPart("body");
builder.addPart("feet");
return builder.result();
}
int main()
{
Foo foo = buildFoo();
for (auto const& part : foo.getParts()) {
std::cout << part << "\n";
}
return 0;
}
But I don't like that I need to specify the friend class (and possibly more, if more builders are added later). So I though about deriving the builder like this:
class Foo
{
public:
[[nodiscard]] std::vector<std::string> const& getParts() const
{
return parts;
}
protected:
std::vector<std::string> parts;
};
class DerivedFooBuilder : public Foo
{
public:
void addPart(std::string name)
{
parts.emplace_back(std::move(name));
}
};
Foo buildFoo()
{
DerivedFooBuilder builder;
builder.addPart("head");
builder.addPart("body");
builder.addPart("feet");
return builder;
}
This makes use of object slicing, which wouldn't be bad here, I guess, since only the non-const methods are sliced away. The members need to be protected instead of private, though. Do you think that's a good approach?
Alternatively I could have some MutableFoo class derived from a ConstFoo class, and make the builder work with a MutableFoo, but return a ConstFoo, again involving object slicing, to throw away all non-const methods. Would that be cleaner?
Or should I just have a Foo class with const and non-const methods, and let the user of the builder make sure the result is const?
IMHO there is no UB here, which at least guarantees that this code should give the expected result.
The downside is that depending on possible copy elision, you will either create a true
DerivedFooBuilderin yourmain, or have to build a completeFoosubobject inside of the builder to later copy it. And either way is likely to be suboptimal.Furthermore,
friendbelongs to the language for a reason. And I would call this the nominal use case: an auxiliary thing needs a access to the private members of another class to allow a better separation of concerns:Fooclass is responsible for all of the standard behaviour of the object it modelsBut if the object is really complex and large, I would not use a builder class, but only a builder free function, which could optionaly use an auxiliary class to provide the initialization. That way would allow an optimal use of the copy elisions at creation time.