Trying to understand "&&"

C

Chameleon

I thought I understood the "&&" until the following simple example did
not work.
Can someone tell me which is the problem or what I did not understand?

It add 2 strings and "cout" the result.

Special thanks for your time!

------------------
g++ -std=c++0x main.cpp
------------------

-- main.cpp ------
#include <iostream>
#include <cstring>
using namespace std;

class A
{
public:
A() : str(0) { cout << "A()" << endl; }
A(const char *string) : str(0) { str = new char[strlen(string) + 1];
strcpy(str, string); cout << "A(const char*)" << endl; }
~A() { delete[] str; cout << "~A()" << endl; }
A(A &a) : str(0) { str = new char[strlen(a.str) + 1]; strcpy(str,
a.str); cout << "A(A&)" << endl; }
A(A &&a) : str(0) { str = a.str; a.str = 0; cout << "A(A&&) : " << str
<< endl; }
A &operator=(A &a) {
delete[] str; str = 0;
str = new char[strlen(a.str) + 1];
strcpy(str, a.str);
cout << "A = &A" << endl;
return *this;
}
A &operator=(A &&a) {
delete[] str;
str = a.str; a.str = 0;
cout << "A = &&A" << endl;
return *this;
}
A &&operator+(A &a) {
A b;
b.str = new char[strlen(this->str) + strlen(a.str) + 1];
strcpy(b.str, this->str);
strcpy(b.str + strlen(this->str), a.str);
cout << "A + A = " << b.str << endl;
return std::move(b);
}
const char *operator*() { return str; }
protected:
char *str;
};

int main()
{
A a("Uni");
A b("verse");
A c = a + b;
cout << *c << endl;
cout << *(a+b) << endl;
return 0;
}
------------------
 
B

Balog Pal

"Chameleon" <[email protected]>

I didn't look into move/&& stuff lately, but this part looks fishy:
A &&operator+(A &a) {
A b;
b.str = new char[strlen(this->str) + strlen(a.str) + 1];
strcpy(b.str, this->str);
strcpy(b.str + strlen(this->str), a.str);
cout << "A + A = " << b.str << endl;
return std::move(b);
}

IIRC std::move is nothing but a visible cast from lvalue ref to rvalue ref
without anything extra. What makes this code similar to

A& foo() {A res; return res; }

that returns dongling reference. (one to an object destroyed before a
possible use).

operator + want to return a new object, so make it return A, not A&&, and
let the rest of the system pick up that rvalue in a sensible way.
 
S

SG

class A
{
public:
    A();
    A(const char *string);
    ~A();
    A(A &a);

The copy constructor usually takes a reference to a CONST.
    A && operator+(A &a) {
        A b;
      b.str = new char[strlen(this->str) + strlen(a.str) + 1];

This this->str as well as a.str could be null pointers couldn't they?
        strcpy(b.str, this->str);
        strcpy(b.str + strlen(this->str), a.str);
        cout << "A + A = " << b.str << endl;
        return std::move(b);
    }

A&& is a reference type. b is a function-local object. You're
returning a reference to a function-local object. This produces a
dangling reference. Using this dangling reference invokes undefined
behaviour. When in doubt, avoid declaring functions that return rvalue
references. This is almost always the wrong thing to do. Your operator
+ should "return an object by value". Also, if you return a function-
local object you should not use std::move as this inhibits a potential
copy/move elision.

Cheers!
SG
 
C

Chameleon

Στις 01/01/2011 22:02, ο/η SG έγÏαψε:
The copy constructor usually takes a reference to a CONST.

Yes, Indeed.
A&& operator+(A&a) {
A b;
b.str = new char[strlen(this->str) + strlen(a.str) + 1];

This this->str as well as a.str could be null pointers couldn't they?

Of course. But it is a very very super fast sample. So I don't check
every case.
A&& is a reference type. b is a function-local object. You're
returning a reference to a function-local object. This produces a
dangling reference. Using this dangling reference invokes undefined
behaviour. When in doubt, avoid declaring functions that return rvalue
references. This is almost always the wrong thing to do. Your operator
+ should "return an object by value". Also, if you return a function-
local object you should not use std::move as this inhibits a potential
copy/move elision.

Cheers!
SG

Thanks a lot!
After 8 years of C++ programming, I realize that return an object by
value, doen't invoke the D'tor of the function-local object.
Whow!!!
This is what I am trying to do, but it exists!.

So, the "&&" needed in case where you must put an object in a
vector<UberObjectClass>. Until now, I used vector<UberObjectClass*>.
Am I right??
 
S

SG

Στις 01/01/2011 22:02, ο/η SG έγÏαψε:
On 1 Jan., 14:42, Chameleon wrote:
     A&&  operator+(A&a) {
         A b;
         b.str = new char[strlen(this->str) + strlen(a.str) + 1];
         strcpy(b.str, this->str);
         strcpy(b.str + strlen(this->str), a.str);
         cout<<  "A + A = "<<  b.str<<  endl;
         return std::move(b);
     }
A&&  is a reference type. b is a function-local object. You're
returning a reference to a function-local object. This produces a
dangling reference. Using this dangling reference invokes undefined
behaviour. When in doubt, avoid declaring functions that return rvalue
references. This is almost always the wrong thing to do. Your operator
+ should "return an object by value". Also, if you return a function-
local object you should not use std::move as this inhibits a potential
copy/move elision.

Thanks a lot!
After 8 years of C++ programming, I realize that return an object by
value, doen't invoke the D'tor of the function-local object.
Whow!!!

That depends. If you return by value and write code like this:

A x = ...;
A y = ...;
A z = x + y;

a good compiler is able to elide two unnecessary copies in the third
line. These unnecessary operations are: 1. Copy-constructing the
return value from the function-local object. 2 Copy-constructing z
from the return value. However, the C++ standard doesn't require these
copy elisions. This is optional and a matter of QoI.

The nice thing with the rvalue reference update is that if the
compiler is not able to elide these unnecessary copies (for whatever
reason), it /has/ to use the move constructor if there is one. What
exactly a "move construction" is depends on how you define your move
constructor. Apart from that, there is no magic involved. So, with
another copiler you might still end up with three distinct objects
(function-local, return value, z) but then the move constructor is
used to construct the latter from the former. The only thing you need
to do if you want to enable move semantics is defining a custom move
constructor (and/or move assignment operator). And sometimes you don't
even need to do that because the compiler will generate appropriate
move operations automatically in some cases. Actually, you should
adopt a class design style that minimizes the need for user-defined
copy/move operations.
So, the "&&" needed in case where you must put an object in a
vector<UberObjectClass>. Until now, I used vector<UberObjectClass*>.
Am I right??

Sorry, I don't understand this question. I think you're saying that
with rvalue references and move semantics we are now able to put
"heavy objects" directly into containers instead of storing pointers.
If so, you're right as long as you ÃœberObjectClass has a cheap move
constructor. The number of copy/move constructions can be further
reduced by using the new emplace functions (emplace, emplace_back,
etc...).

Cheers!
SG
 

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,744
Messages
2,569,481
Members
44,900
Latest member
Nell636132

Latest Threads

Top