Reputation: 175
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
Reputation: 73366
I'd suppose that you start line counting at 1 ;-)
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 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.
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;
}
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