strange copy constructor with g++

H

hfinster

Hello,

could somebody please shed light on the following problem with g++
(4.03 and 3.3.6 as well)?
Obviously, the copy constructor is not executed, if I assign the
result of a function call to a (new) variable. Instead the memory
location (address) of a temporary result variable from the function is
used.

This is my test code:


#include <iostream>

class Foo {
public:

Foo() {
std::cerr << "Foo:ctor\n";
}

Foo( const Foo& f ) {
std::cerr << "Foo:cctor\n";
}

};


class Bar {
public:

const Foo f() {
Foo res;
std::cerr << " r " << &res << "\n";
return res;
}

};

int main() {

Bar b;

//
// copy constructor not used
// why?
// the address of f1 is identical to the temporary result object in
f()
//
Foo f1 = b.f();
std::cerr << " f1 " << &f1 << std::endl;

/// result:
/// Foo::ctor
/// r address1
/// f1 address1 (identical)
/// the compiler simply uses 'res' as 'f1'
///



Foo f2;

//
// copy constructor used as expected
//
Foo f3 = f2;
std::cerr << " f2 " << &f2 << std::endl; // different objects
std::cerr << " f3 " << &f3 << std::endl; // (as expected)

///
/// Foo::cctor
/// f2 address2
/// f3 address3 (different)
///
}


Of course, this behaviour is 'optimal' in some sense, as it does not
require copying the contents of the result variable 'res' to 'f1'.
However, there is a problem, if Foo has pointers and f1 requires a
deep copy of data.

Thanks for any hint.

Harald

BTW: if I compile the same code with Embedded Visual C , the behaviour
is as expected (as I expect).
 
K

Kai-Uwe Bux

hfinster said:
Hello,

could somebody please shed light on the following problem with g++
(4.03 and 3.3.6 as well)?
Obviously, the copy constructor is not executed, if I assign the
result of a function call to a (new) variable. Instead the memory
location (address) of a temporary result variable from the function is
used. [code snipped]
Of course, this behaviour is 'optimal' in some sense, as it does not
require copying the contents of the result variable 'res' to 'f1'.
However, there is a problem, if Foo has pointers and f1 requires a
deep copy of data.

The standard explicitly allows this behavior [12.8/15]:

When certain criteria are met, an implementation is allowed to omit the
copy construction of a class object, even if the copy constructor and/or
destructor for the object have side effects. ...

The clause goes on to describe in detail, when the compiler is allowed to
elide copy-constructor calls. Suffice it to say, that your case is one of
them. (I think, this one is called RVO: return value optimization.)


Best

Kai-Uwe Bux
 
K

Kai-Uwe Bux

hfinster said:
Hello,

could somebody please shed light on the following problem with g++
(4.03 and 3.3.6 as well)?
Obviously, the copy constructor is not executed, if I assign the
result of a function call to a (new) variable. Instead the memory
location (address) of a temporary result variable from the function is
used. [snip]
Of course, this behaviour is 'optimal' in some sense, as it does not
require copying the contents of the result variable 'res' to 'f1'.

The above, I addressed else-thread.
However, there is a problem, if Foo has pointers and f1 requires a
deep copy of data.

There should be no problem. When you do

Foo f1 = some_function(); // RVO kicks in

then the compiler has license to just construct f1 in place: your function
has to create a local Foo anyway, and that constructor is just told to use
the place for f1 directly. All necessary pointers will be created.


Best

Kai-Uwe Bux
 
P

peter koch

Hello,

could somebody please shed light on the following problem with g++
(4.03 and 3.3.6 as well)?
Obviously, the copy constructor is not executed, if I assign the
result of a function call to a (new) variable. Instead the memory
location (address) of a temporary result variable from the function is
used.
[snip]

This is called RVO (return value optimisation) and is explicitly
allowed in the standard. Generally speaking, the compiler is allowed
to eliminate copying of elements, even if this changes behavior (as
your snipped example does). This basically means that a copy is a copy
and nothing else: do not rely on this to do anything more.
Of course, this behaviour is 'optimal' in some sense, as it does not
require copying the contents of the result variable 'res' to 'f1'.
However, there is a problem, if Foo has pointers and f1 requires a
deep copy of data.
No, I do not believe it causes any problems. If so, you must give us
an example.


/Peter
 
H

hfinster

Hello,

thanks for your fast response!

Maybe, I have to explain, why the RVO behaviour causes trouble in my
case.
The concrete problem is a Vector class. A Vector 'owns' data (T *
data;)
Now, I have tow types of Vectors, say RowVector and ColumnVector and a
transpose-operator t(), i.e.
RowVector ColumnVector::t()
ColumnVector RowVector::t()
In many cases, the result is just required temporarily and 'read
only', i.e. it would be sufficient for t() to return a shallow copy of
the original vector giving better performance.
For example:
const ColumnVector & cv = row.t();
would allow 'read only' access to cv,
whereas
ColumnVector cv = row.t();
would also allow read/write without modifications to the original
'row' IF the assignment would automatically copy the data (via copy
constructor).
However, I see, that this approch is still dangerous, if I modify
'row' and use it's 'transposed copy' cv.

Conclusion: I will have to modify my concept.

Thanks again for you feedback!

Harald
 
H

hfinster

No, I do not believe it causes any problems. If so, you must give us
an example.

sorry, next try:

class RowVector {

....
//
// the transpose method returns a Vector with a shallow copy of the
original data
// this avoids expensive copy-operations of the data, if the
transposed vector is
// used as a const (read only)
//
ColumnVector transpose() {
ColumnVector result;
result.data = this->data; // shallow copy
return result;
}

T * data;
};

class ColumnVector {

ColumnVector( const ColumnVector & in ) {
make a deep copy of data
}

....
T * data;
};


test() {
RowVector rv = { 1, 2, 3 };
cerr << rv.t() ; // prints the transposes vector, shallow copy is ok

//
// without copy ctor:
//
ColumnVector cv = rv.t(); // shallow copy
cv[1] = 5;
modifies rv, thus rv bevomes { 1, 5, 3 }

//
// with copy ctor
//
ColumnVector cv = rv.t(); // deep copy
cv[1] = 5;
rv is unmodified { 1, 2, 3 }


This behaviour can be achieved with:

ColumnVector cv;
cv = rv.t();


}
 
H

Heinz Ozwirk

hfinster said:
No, I do not believe it causes any problems. If so, you must give us
an example.

sorry, next try:

class RowVector {

...
//
// the transpose method returns a Vector with a shallow copy of the
original data
// this avoids expensive copy-operations of the data, if the
transposed vector is
// used as a const (read only)
//
ColumnVector transpose() {
ColumnVector result;
result.data = this->data; // shallow copy
return result;
}

T * data;
};

class ColumnVector {

ColumnVector( const ColumnVector & in ) {
make a deep copy of data
}

...
T * data;
};


test() {
RowVector rv = { 1, 2, 3 };
cerr << rv.t() ; // prints the transposes vector, shallow copy is ok

//
// without copy ctor:
//
ColumnVector cv = rv.t(); // shallow copy
cv[1] = 5;
modifies rv, thus rv bevomes { 1, 5, 3 }

//
// with copy ctor
//
ColumnVector cv = rv.t(); // deep copy
cv[1] = 5;
rv is unmodified { 1, 2, 3 }


This behaviour can be achieved with:

ColumnVector cv;
cv = rv.t();


}

The problem in this (bad) example is not RVO, but your code. Once you copy
the pointer to RowVector's internal data into the temporary ColumnVector,
you have to pointers in two instances of different classes to the same piece
of memory. If your ColumnVector and RowVector classes were well written,
their destructors would free the memory used by their internal data. But
which one is supposed to free the memory shared by the RowVector for which
you called RowVector::transpose() and the local variable used in that
function? If ColumnVector would properly free it, you'd have a dangling
pointer in RowVector. And if it didn't, who would free the memory used by
the resulting ColumnVector? If ColumnVector's dtor doesn't free it, you'll
have a memory leak. So either way, you have to think about your concept.

So better hope for RVO to work and do a deep copy when constructing a
ColumnVector from a RowVector. Using std::vector instead of a raw pointer
would be helpfull, but not with EVC (IIRC). But even there you could write
your own simple vector class.

HTH
Heinz
 
H

hfinster

Hello

"hfinster" <[email protected]> schrieb im Newsbeitragnews:[email protected]...
The problem in this (bad) example is not RVO, but your code. Once youcopy
the pointer to RowVector's internal data into the temporary ColumnVector,
you have to pointers in two instances of different classes to the same piece
of memory.
right

If your ColumnVector and RowVector classes were well written,
their destructors would free the memory used by their internal data. But
which one is supposed to free the memory shared by the RowVector for which
you called RowVector::transpose() and the local variable used in that
function?

the sample code does not show the 'whole truth'.
I am aware of the problem, you mention and keep track of the memory
ownership. (via reference counter)
If ColumnVector would properly free it, you'd have a dangling
pointer in RowVector.

ColumnVector (in this example) 'knows', that it's data pointer points
to data 'owned' by RowVector.
So, it does not free the momory in it's dtor.
...
So better hope for RVO to work and do a deepcopywhen constructing a
ColumnVector from a RowVector. Using std::vector instead of a raw pointer
would be helpfull, but not with EVC (IIRC). But even there you could write
your own simple vector class.

My Vector class has been written, when I still had to use EVC. As this
is no longer true, I will rewrite Vector based on std::vector as you
suggest.

Thanks to all for your excellent contributions!

Greetings Harald
 
G

Grizlyk

hfinster wrote:

I you want to see any answer, try to write here _very short, but
compileable_ example, because it is not easy to guess what do you want to
say and what the error and where. It is not enough to write here undeclared
memers or abstract words as "shallow copy", "expensive copy-operations" and
"make a deep copy".

If you are writing large code with many unnecessary parts, but the code can
not be compiled, may be you just can not strict build your question firstly
to yourself? Really, I can not see in your example a point where error can
be appeared due to undeclared names, because I can not understand what the
code must do.

In the group about 100 new messages per day and if reader will try to guess
for each, the reader will spend here 24 hour of day.

template said:
class RowVector
{
public:

// the transpose method returns a Vector with a shallow copy
// of the original data this avoids expensive copy-operations
// of the data, if the transposed vector is used as a const (read only)

ColumnVector transpose()

better to place ColumnVector declaration befor RowVector
{
ColumnVector result;
result.data = this->data; // shallow copy
return result;
}

T *data;

what does it means: pointer to static or dynamic?
who will delete data?


template said:
class ColumnVector
{
public:


ColumnVector( const ColumnVector & in )
{

//make a deep copy of data
what kind of copy will be?
}

...
T * data;

what does it means: pointer to static or dynamic?
who will delete data?
};


test() {

no return type
RowVector rv = { 1, 2, 3 };

RowVector can not be created like this
cerr << rv.t() ; // prints the transposes vector, shallow copy is ok

assuming ColumnVector(cosnt ColumnVector&) will not be called
there is no member RowVector::t
there is no "operator<<" for pair cerr, ColumnVector
//
// without copy ctor:
//
ColumnVector cv = rv.t(); // shallow copy

assuming ColumnVector(cosnt ColumnVector&) will not be called
there is no member RowVector::t
cv[1] = 5;

there is no member ColumnVector::eek:perator[]
//
// with copy ctor
//
ColumnVector cv = rv.t(); // deep copy

assuming ColumnVector(cosnt ColumnVector&) will be called
cv[1] = 5;


there is no member ColumnVector::eek:perator[]
//rv is unmodified { 1, 2, 3 }

Really, why do you want to use undeclared names with hidden behaviour insted
of well defined operations to show error? What if error is inside undeclared
members?
 

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

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,130
Latest member
MitchellTe
Top