Michael Schäfer
Michael Schäfer

Reputation: 175

C++ different constructor calls

I wrote a little class to learn the different constructor calls

#include <iostream>
#include <cstdlib>
#include <cstring>

class String {
    private:
        char *str;

    public:
        explicit String(const char *p);
        String(String&& StringObject);
        String(String& stringObject);
        ~String();
        friend std::ostream& operator<<(std::ostream& os, const String& s);
        friend String operator+(const String& s1, const String& s2);
};

String::String(const char *p)
{
    size_t l = strlen(p) + 1;
    str = (char *)malloc(sizeof(char) * l);
    if (str)
        strcpy(str, p);
    std::cout << "constructor call" << std::endl;
}
String::String(String& stringObject)
{
    str = (char *)malloc(sizeof(char) * (strlen(stringObject.str) + 1));
    strcpy(str, stringObject.str);
    std::cout << "copy constructor call" << std::endl;
}

String::~String()
{
    free(str);
}

String::String(String&& stringObject)
{
    this->str = stringObject.str;
    stringObject.str = nullptr;
    std::cout << "move constructor call" << std::endl;
}

std::ostream& operator<<(std::ostream& os, const String& s)
{
    return os << s.str;
}

String operator+(const String& s1, const String& s2)
{
    size_t sl1 = strlen(s1.str);
    size_t sl2 = strlen(s2.str);
    char str[sl1 + sl2 + 1];
    strcpy(str, s1.str);
    strcpy(str+sl1, s2.str);

    return String{str};
}

String doNothing(String obj)
{
    std::cout << "output in function: " << obj << std::endl;

    return obj;
}

int main()
{
    String s1("text");
    String s2("and more text");

    std::cout << "output: " << s1 << std::endl;
    String s3 = doNothing(s1+ String{" "} + s2);
    String s4 = doNothing(s1);

    String s5 = s1 + s4;
}

The output is

constructor call
constructor call
output: text
constructor call
constructor call
constructor call
output in function: text and more text
move constructor call
copy constructor call
output in function: text
move constructor call
constructor call

I think the constructor calls on line 4 to 6 come from the method call

String s3 = doNothing(s1+ String{" "} + s2);

Why does the method call not cause a call to the copy constructor like the second method call?

String s4 = doNothing(s1);

Maybe because s1 is a lvalue?

Can the move constructor only be called when a function returns an object or also when a reference or pointer to an object is returned?

Upvotes: 1

Views: 101

Answers (1)

Christophe
Christophe

Reputation: 73366

I'd suppose that you start line counting at 1 ;-)

The case with a complex expression

We would expect the following statement to construct in principe a temporary String for String{" "}, and then a temporary string for each of the two +. Since the parameter obj is to be constructed from a temporary object, the move constructor could be used:

String s3 = doNothing(s1+ String{" "} + s2);

The idea, of the move constructor is to be able to take advantage of the fact that the original object is disposable.

The return value of the function is also a temporary value and this one is used to construct s3 with the move constructor.

But shouldn't we then have 3 constructors and two move constructors? No, because there are copy elision rules. These cause the compiler to avoid unnecessary a copies/moves, and construct directly into the target. This happens here for the parameter.

The case with the lvalue

The next statement constructs the parameter obj with the copy constructor of an lvalue s1:

String s4 = doNothing(s1);

This is less surprising. The value is in principle copied to a temporary return value, which is then used to move-construct s4 from a temporary.

But again, copy elision simplifies this to a copy constructor and a move.

Analysing what happens

You could analyse more in detail what happens, by displaying the address of the object. This is very helpful to understand what happens on what object:

class String {...};

String::String(const char *p)
{
    size_t l = strlen(p) + 1;
    str = new char[l];
    if (str)
        strcpy(str, p);
    std::cout << "constructor call:" << this<<"="<<str<< std::endl;
}
String::String(const String& stringObject)
{
    str = new char[ strlen(stringObject.str) + 1];
    strcpy(str, stringObject.str);
    std::cout << "copy constructor call:" <<this<<"="<<str<< std::endl;
}

String::~String()
{
    delete[] str;
}

String::String(String&& stringObject)
{
    this->str = stringObject.str;
    stringObject.str = nullptr;
    std::cout << "move constructor call:"<<this <<"="<<str<< std::endl;
}

String operator+(const String& s1, const String& s2)
{
    size_t sl1 = strlen(s1.str);
    size_t sl2 = strlen(s2.str);
    char str[sl1 + sl2 + 1];
    strcpy(str, s1.str);
    strcpy(str+sl1, s2.str);

    return String{str};
}

String doNothing(String obj)
{
    std::cout << "output in function: " <<&obj<<":"<< obj << std::endl;
    return obj;
}

Online demo

Unrelated advice

I highly recommend that you get rid of malloc() and free() in C++ so I used here new[] and delete[] (or new and delete if it's not about arrays). In a second step you could alsoe get rid of the strcpy()

Upvotes: 3

Related Questions