1

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.

DeSpeaker
  • 1,159
  • 1
  • 11
  • 11
  • `CodeBuilder code{"Dog"}.add_field("name", "std::string").add_field("legs", "int");`? – NathanOliver Aug 17 '21 at 21:20
  • You are logging the constructions/destructions of the `MyString` class, not the `CodeBuilder` class. Of course a default `MyString` object is created when a `CodeBuilder` object is default-constructed, and a copied `MyString` is created when a copy-constructed `CodeBuilder` is created. You are creating a temp `CodeBuilder` object with `CodeBuilder{...}...` and then copy-constructing `code` from that temp. Both objects have to be destroyed. This is exactly what your logging is showing you. Why does this surprise you? – Remy Lebeau Aug 17 '21 at 21:20
  • ```CodeBuilder code{"Dog"}.add_field("name", "std::string").add_field("legs", "int");``` I tried this way. It doesn't compile, i'm not sure why. Regarding the other answer, i can also log the copy constructor of CodeBuilder and show you the new output if it clarifies my question. – DeSpeaker Aug 17 '21 at 21:24
  • 1
    `CodeBuilder code{"Dog"}.add_field("name", "std::string").add_field("legs", "int");` doesn't compile because it is not legal code. To have only 1 constructor called, you are just going to have to split the `add_fields()` calls into a separate expression, eg: `CodeBuilder code{"Dog"}; code.add_field("name", "std::string").add_field("legs", "int");` – Remy Lebeau Aug 17 '21 at 21:25
  • ```CodeBuilder code{"Dog"}; code.add_field("name", "std::string").add_field("legs", "int");``` is good alternative (and i tried it), however i wanted to use the builder pattern in a way that the object is constructed in the same instruction (at least this is how they used it in the course) – DeSpeaker Aug 17 '21 at 21:29
  • Ah yeah, woops. That isn't going to work. – NathanOliver Aug 17 '21 at 21:31
  • Then either construct the object dynamically via `new` or equivalent, or simply don't worry about multiple constructors being used, and just implement move operations if needed.. – Remy Lebeau Aug 17 '21 at 21:32
  • Thank you for your answers. Just to clarify again, I understand why the copy constructor is invoked in the first example, but that's not the issue in this post. The issue is how to optimize the building process and also (not mentioned in the post) have a solution for classes with non-copyable objects. – DeSpeaker Aug 17 '21 at 21:53

1 Answers1

1

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
bolov
  • 72,283
  • 15
  • 145
  • 224