DeSpeaker
DeSpeaker

Reputation: 1339

Avoid invoking multiple constructors when using the builder pattern in c++

Consider the following piece of code:

CodeBuilder code = CodeBuilder{"Dog"}.add_field("name", "std::string").add_field("legs", "int");

and the following implementation:

class CodeBuilder
{
    MyString _code;
public:

    CodeBuilder(const std::string& class_name) //Initialize _code
    {
    }

    CodeBuilder& add_field(const std::string& name, const std::string& type)
    {
        //Some implementation
        return *this;
    }
};

and MyString class:

class MyString
{
    std::string str;

public:
    MyString()
    {
        std::cout << "Default Ctor" << std::endl;
    }

    ~MyString()
    {
        std::cout << "Destructor" << std::endl;
    }

    MyString(const MyString & other) : str(other.str)
    {
        std::cout << "Copy Ctor" << std::endl;
    }

    MyString & operator = (const MyString & other)
    {
        if(&other != this)
        {
            str = other.str;
        }

        std::cout << "Copy Assignment Operator" << std::endl;

        return *this;
    }
};

When I run the first block of code, the copy constructor (of CodeBuilder) is also invoked and this is the output:

Default Ctor
Copy Ctor
Destructor
Destructor

I then tried to change the above code to (i.e. adding reference):

CodeBuilder & code = CodeBuilder{"Dog"}.add_field("name", "std::string").add_field("legs", "int");

and only the default constructor was called, but this is really bad idead since this way i'm keeping reference to an already destructed object (and when i tried to print the results, gibberish was printed).
I wanted to know if there is a way to both invoke only one constructor (i.e. somehow construct the object directly to my variable) and still get the correct results ?
The only optimization i could think of is moving the temporary object instead of copying it.

Upvotes: 1

Views: 320

Answers (1)

bolov
bolov

Reputation: 75785

If you preserve the reference type on add_field:

class CodeBuilder
{
    MyString _code;

    void add_field_impl(const std::string& name, const std::string& type);

public:

    /* ... */

    
    CodeBuilder& add_field(const std::string& name, const std::string& type) &
//             ^                                                             ^
//             lvalue                                                   lvalue
    {
        add_field_impl(name, type);
        return *this;
    }

    CodeBuilder&& add_field(const std::string& name, const std::string& type) &&
//             ^                                                              ^
//             rvalue                                                    rvalue
    {
        add_field_impl(name, type);
        return std::move(*this);
    }
};

the move constructor will be invoked instead without any modifications on the calling site:

CodeBuilder code = CodeBuilder{"Dog"}.add_field("name", "std::string")
                                     .add_field("legs", "int");
//               ^
//               move constructor

Upvotes: 1

Related Questions