Return value optimization

  • Thread starter Christian Meier
  • Start date
C

Christian Meier

Hi NG

A few days ago there was a thread in comp.lang.c++.moderated with the
subject "Return versus Side-Effect". The OP asked whether to use "return by
value" or an additional parameter of type "C&" which will be overwritten
with a result.
The most posters advised the OP to use the version with the return value. No
only for aesthetic reasons but also for performance reasons because of the
return value optimization.
I wrote a little sample program to test their assertion.
For aesthetic reasons I prefer the "return by value" version. But I found
out that for performance reasons both versions are necessary. It depends on
how you call the function. Sometimes the "return by value" is faster than
the "out-parameter" version and sometimes the other way around. Can you
confirm my conclusion?

Best regards,
Chris





Here is my test code:


#include <iostream>
using std::cout;

#include <iostream>
using std::cout;

class MyString
{
public:
MyString() : value(NULL) { cout << "ctor 0\n"; }
MyString(const char*);
MyString(const MyString&);
~MyString() { delete[] value; }

MyString& operator=(const char* newValue);
MyString& operator=(const MyString&);

private:
char* value;
};


MyString::MyString(const char* newValue)
{
cout << "ctor 1\n";
if (newValue == NULL) {
value = NULL;
} else {
int length = strlen(newValue);
value = new char[length + 1];
strcpy(value, newValue);
}
}


MyString::MyString(const MyString& other)
{
cout << "copy ctor\n";
if (other.value == NULL) {
value = NULL;
} else {
int length = strlen(other.value);
value = new char[length + 1];
strcpy(value, other.value);
}
}


MyString& MyString::eek:perator=(const char* newValue)
{
cout << "op =0\n";
if (value != NULL) {
delete[] value;
} // if

if (newValue == NULL) {
value = NULL;
} else {
int length = strlen(newValue);
value = new char[length + 1];
strcpy(value, newValue);
}
return *this;
}


MyString& MyString::eek:perator=(const MyString& other)
{
cout << "op =1\n";
if (this == &other) {
return *this;
} // if

if (value != NULL) {
delete[] value;
} // if

if (other.value == NULL) {
value = NULL;
} else {
int length = strlen(other.value);
value = new char[length + 1];
strcpy(value, other.value);
}

return *this;
}


MyString returnByVal()
{
MyString ret("return value");
return ret;
}


void returnByParam(MyString& out)
{
out = "return value";
}



int main()
{
// First test case where the out parameter is faster.
cout << "Before returnByVal()\n";
MyString str1;
str1 = returnByVal();
cout << "\nBefore returnByParam()\n";
MyString str2;
returnByParam(str2);
cout << "Finished test 1\n\n\n";

// Second test case where return by value is faster.
cout << "Before returnByVal()\n";
MyString str3 = returnByVal();
cout << "\nBefore returnByParam()\n";
MyString str4;
returnByParam(str4);
cout << "Finished test 2\n";
}





The output is the following:

Before returnByVal()
ctor 0
ctor 1
op =1

Before returnByParam()
ctor 0
op =0
Finished test 1


Before returnByVal()
ctor 1

Before returnByParam()
ctor 0
op =0
Finished test 2


In the first test case the "out-parameter" version is faster.
In the second test case the "return by value" version is faster.
 
G

gpderetta

Hi NG

A few days ago there was a thread in comp.lang.c++.moderated with the
subject "Return versus Side-Effect". The OP asked whether to use "return by
value" or an additional parameter of type "C&" which will be overwritten
with a result.
The most posters advised the OP to use the version with the return value. No
only for aesthetic reasons but also for performance reasons because of the
return value optimization.
I wrote a little sample program to test their assertion.
For aesthetic reasons I prefer the "return by value" version. But I found
out that for performance reasons both versions are necessary. It depends on
how you call the function. Sometimes the "return by value" is faster than
the "out-parameter" version and sometimes the other way around. Can you
confirm my conclusion?
[...]

Change this:
MyString& MyString::eek:perator=(const MyString& other)
{
cout << "op =1\n";
if (this == &other) {
return *this;
} // if

if (value != NULL) {
delete[] value;
} // if

if (other.value == NULL) {
value = NULL;
} else {
int length = strlen(other.value);
value = new char[length + 1];
strcpy(value, other.value);
}
return *this;
}

Into:

MyString& MyString::eek:perator=(MyString other)
{
value = other.value;
other.value = 0;
cout << "op =1\n";
return *this;
}

the output doesn't change, but now the "op=1" operation is quite
inexpensive.
 
Y

Yannick Tremblay

Deleted String class too complex to best illustrate NVRO.
All you need is a very simply class the cout
Constuctor/Copyconstructor/operator=. All the rest obfuscate.
int main()
{
// First test case where the out parameter is faster.
cout << "Before returnByVal()\n";
MyString str1;

You create the object using the default constructor for no reason.
If you tell the compiler that it must create a default initialised
object then that is what it does even if a human code reviewer would
question why you felt the need to default initialise it and
immediately assign to it. The compiler can't send you an email
asking: "Are you sure that's really what you want to do?"
str1 = returnByVal();

then assign to it
cout << "\nBefore returnByParam()\n";
MyString str2;
returnByParam(str2);
cout << "Finished test 1\n\n\n";

// Second test case where return by value is faster.
cout << "Before returnByVal()\n";
MyString str3 = returnByVal();

Here you create and initialise correctly all at once. That's better
cout << "\nBefore returnByParam()\n";
MyString str4;
returnByParam(str4);
cout << "Finished test 2\n";
}
The output is the following:

Before returnByVal()
ctor 0
ctor 1
op =1

Before returnByParam()
ctor 0
op =0
Finished test 1


Before returnByVal()
ctor 1

And your test shows that there are less methods called.
Before returnByParam()
ctor 0
op =0
Finished test 2


In the first test case the "out-parameter" version is faster.
In the second test case the "return by value" version is faster.

Hmm, maybe. You have only traced number of methods called. Not speed
so you are assuming that this is faster. It might not be and it might
not be relevant.

Yan
 
C

Christian Meier

You create the object using the default constructor for no reason.
If you tell the compiler that it must create a default initialised
object then that is what it does even if a human code reviewer would
question why you felt the need to default initialise it and
immediately assign to it. The compiler can't send you an email
asking: "Are you sure that's really what you want to do?"

It was my intension to do this in my first test case.
I could rewrite it to something like this:

MyString str1 = "Hello";
str1 += getWorldStringAsReturnValue();

It is not unusual that you have an already created string and you assign
something new (a return value of a function) to it.
I tried to cover this case by my first test.

Hmm, maybe. You have only traced number of methods called. Not speed
so you are assuming that this is faster. It might not be and it might
not be relevant.

Yan



"op =1" has quite the same statements as "op =0" except the additional test
in "op =1". I'm pretty sure that "op =0" is faster than "ctor 1" + "op =1".
But I agree that the speed difference might not be relevant.


Regards,
Chris
 
C

Christian Meier

Change this:
MyString& MyString::eek:perator=(const MyString& other)
{
cout << "op =1\n";
if (this == &other) {
return *this;
} // if

if (value != NULL) {
delete[] value;
} // if

if (other.value == NULL) {
value = NULL;
} else {
int length = strlen(other.value);
value = new char[length + 1];
strcpy(value, other.value);
}
return *this;
}

Into:

MyString& MyString::eek:perator=(MyString other)
{
value = other.value;
other.value = 0;
cout << "op =1\n";
return *this;
}

the output doesn't change, but now the "op=1" operation is quite
inexpensive.


This would decrease performance of the statements

MyString str5("asdf");
MyString str6("qwert");
str 5 = str6;

because of the copy constructor call which is necessary for the "by
value"-parameter.

Regards,
Chris
 
G

gpderetta

Change this:
MyString& MyString::eek:perator=(const MyString& other)
{
cout << "op =1\n";
if (this == &other) {
return *this;
} // if
if (value != NULL) {
delete[] value;
} // if
if (other.value == NULL) {
value = NULL;
} else {
int length = strlen(other.value);
value = new char[length + 1];
strcpy(value, other.value);
}
return *this;
}

MyString& MyString::eek:perator=(MyString other)
{
value = other.value;
other.value = 0;
cout << "op =1\n";
return *this;
}
the output doesn't change, but now the "op=1" operation is quite
inexpensive.

This would decrease performance of the statements

MyString str5("asdf");
MyString str6("qwert");
str 5 = str6;

because of the copy constructor call which is necessary for the "by
value"-parameter.

No, it would be exaclty the same, because you need to do a copy
anyways.

With your implementation you do it in operator=, with mine you do it
in the constructor, so it is exactly the same number of copies.

Of course the real problem is that my implementation of operator=
leaks memory. Here is the correct one:

MyString& MyString::eek:perator=(MyString other)
{
std::swap(value, other.value);
cout << "op =1\n";
return *this;
}

A better way would be to implement MyString::swap and implement
operator= in term of swap.
 
Y

Yannick Tremblay

It was my intension to do this in my first test case.
I could rewrite it to something like this:

MyString str1 = "Hello";
str1 += getWorldStringAsReturnValue();

It is not unusual that you have an already created string and you assign
something new (a return value of a function) to it.
I tried to cover this case by my first test.

Yes that is true. However, how would you write the above with:
void getWorldStringAsParam( MyString & str)

Euh???

MyString str1 = "Hello";
MyString tmp;
getWorldStringAsParam(tmp);
str1 += tmp;

Looks a lot worse to me. Unless you start changing the interface for a
special case one like:
void appendWorldStringToExistingStringParam(MyString & str)

but that seems even worse to me.

Yan
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,053
Latest member
BrodieSola

Latest Threads

Top