I'm trying to figure out the cleanest way to implement a fluent interface with unique_ptr
and other "modern" C++ language constructs. Here's my first attempt:
#include <iostream>
#include <memory>
#include <vector>
// Backported from C++14
namespace std
{
template<typename T, typename ...Args>
std::unique_ptr<T> make_unique(Args &&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
}
using namespace std;
class Widget
{
Widget() = delete;
Widget(Widget const &) = delete;
Widget(Widget &&) = delete;
Widget &operator=(Widget const &) = delete;
Widget &operator=(Widget &&) = delete;
public:
Widget(string const &name) :
m_name(name)
{ }
~Widget()
{ }
string const &name() const
{ return m_name; }
private:
string const m_name;
};
class WidgetBuilder
{
WidgetBuilder(WidgetBuilder const &) = delete;
WidgetBuilder(WidgetBuilder &&) = delete;
WidgetBuilder &operator=(WidgetBuilder const &) = delete;
WidgetBuilder &operator=(WidgetBuilder &&) = delete;
public:
WidgetBuilder()
{ }
~WidgetBuilder()
{ }
WidgetBuilder &name(string const &name)
{ m_name = name; return *this; }
unique_ptr<Widget> create()
{ return make_unique<Widget>(m_name); }
private:
string m_name;
};
class WidgetContainer
{
WidgetContainer() = delete;
WidgetContainer(WidgetContainer const &) = delete;
WidgetContainer(WidgetContainer &&) = delete;
WidgetContainer &operator=(WidgetContainer const &) = delete;
WidgetContainer &operator=(WidgetContainer &&) = delete;
public:
WidgetContainer(
string const &firstName,
string const &lastName,
unique_ptr<vector<unique_ptr<Widget>>> widgets) :
m_firstName(firstName),
m_lastName(lastName),
m_widgets(move(widgets))
{ }
~WidgetContainer()
{ }
string const &firstName() const
{ return m_firstName; }
string const &lastName() const
{ return m_lastName; }
vector<unique_ptr<Widget>> const &widgets() const
{ return *m_widgets; }
private:
string const m_firstName;
string const m_lastName;
unique_ptr<vector<unique_ptr<Widget>>> m_widgets;
};
class WidgetContainerBuilder
{
WidgetContainerBuilder(WidgetContainerBuilder const &) = delete;
WidgetContainerBuilder(WidgetContainerBuilder &&) = delete;
WidgetContainerBuilder &operator=(WidgetContainerBuilder const &) = delete;
WidgetContainerBuilder &operator=(WidgetContainerBuilder &&) = delete;
public:
WidgetContainerBuilder()
{ }
~WidgetContainerBuilder()
{ }
WidgetContainerBuilder &firstName(string const &firstName)
{ m_firstName = firstName; return *this; }
WidgetContainerBuilder &lastName(string const &lastName)
{ m_lastName = lastName; return *this; }
WidgetContainerBuilder &addWidget(WidgetBuilder &widgetBuilder)
{
m_widgetBuilders.push_back(&widgetBuilder);
return *this;
}
unique_ptr<WidgetContainer> create()
{
unique_ptr<vector<unique_ptr<Widget>>> widgets =
make_unique<vector<unique_ptr<Widget>>>();
for (auto &widgetBuilder : m_widgetBuilders)
{
widgets->push_back(widgetBuilder->create());
}
return make_unique<WidgetContainer>(
m_firstName,
m_lastName,
move(widgets));
}
private:
string m_firstName;
string m_lastName;
vector<WidgetBuilder *> m_widgetBuilders;
};
int main(int argc, char *argv[])
{
unique_ptr<WidgetContainer> x(
WidgetContainerBuilder()
.firstName("my-first-name")
.lastName("my-last-name")
.addWidget(
WidgetBuilder()
.name("my-widget-0"))
.addWidget(
WidgetBuilder()
.name("my-widget-1"))
.create());
cout << x->firstName() << " " << x->lastName() << endl;
for (auto &widget : x->widgets())
{
cout << widget->name() << endl;
}
return 0;
}
The requirements of the design is that the fully constructed objects of the WidgetContainer
and Widget
"final" types should be immutable: properties of these classes must only be settable during construction and unchangeable thereafter. In order to construct instances of these classes, I've written WidgetContainerBuilder
and WidgetBuilder
. The other constraint is that I need to be able to manage the lifetimes of the final types using unique_ptr
.
The example I give here works and has no obvious bugs, as far as I can tell. However, I would be interested in feedback about potential issues or approaches I might take to improve the cleanliness or fluency of the API.