7
\$\begingroup\$

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.

\$\endgroup\$
3
\$\begingroup\$

Please don't do this:

using namespace std;

See: Why is “using namespace std;” considered bad practice?

I would change a couple of things:

In WidgetBuilder

I don't like the explicit call to create() WidgetBuilder.

unique_ptr<Widget> create()
{ return make_unique<Widget>(m_name); }

I would replace it with a conversion operator:

operator std::unique_ptr<Widget>()
{    return std::make_unique<Widget>(m_name);}

Then usage becomes:

unique_ptr<Widget>  val = WidgetBuilder().name("Loki");

In WidgetContainer

I don't see the need to put the std::vector inside a std::uniqu_ptr.

unique_ptr<vector<unique_ptr<Widget>>> m_widgets;

I would just do

std::vector<std::unique_ptr<Widget>>> m_widgets;

Because of RVO and NRVO this is very efficient when returning objects. Also with the use of "Move Semantic" even passing it as a parameter is now very efficient.

In WidgetContainerBuilder

Like WidgetBuilder remove the create() method.

Not sure I would pass WidgetBuilder to the addWidget() method. Why not a std::unique_ptr<Widget> create them on the fly and use them as needed. (I suppose this is why you had an explicit create() method on Widget.

WidgetContainerBuilder &addWidget(WidgetBuilder &widgetBuilder)
\$\endgroup\$
  • \$\begingroup\$ +1: Agree with you totally about using namespace std except when I'm prototyping like this. Thanks for the comments! \$\endgroup\$ – Richard Cook Jul 7 '14 at 21:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.