About FAQ 10.18

J

James Aguilar

Hey all, I have a quick question with FAQ 10.18. I was running into a
problem similar to this yesterday, fortunately, Victor Bazarov helped me out
there. I do have a question about the way the compiler interprets this
line, the workaround for the problem of using a temporary in a constructor:

Foo x = Foo(Bar());

My question is, what exactly does this do? Do is create a default
constructed Foo and then overwrite it using the assignment operator with
whatever is on the right side? Or does it simply initialize a Foo object
with whatever is on the right side? Can I count on this to happen the same
way everywhere? Lastly, is there any way that this pattern of using
assignment can hurt me silently (i.e. performance-wise or any other way)?

Sorry if the question seems obscure or useless. It's just that I plan on
doing this for a long time, and I'd like to know now how I need to proceed
so I don't shoot myself in the foot later. =)

- JFA
 
J

James Aguilar

A quick note: I tried looking at the assembly code of a short test program I
wrote. Here is what came out:

The program is:

int main()
{
int x(10);
int y = 20;
return 0;
}

The assembly is:

_main:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
andl $-16, %esp
movl $0, %eax
movl %eax, -12(%ebp)
movl -12(%ebp), %eax
call __alloca
call ___main
movl $10, -4(%ebp) /*<--Does x*/
movl $20, -8(%ebp) /*<--Does y*/
movl $0, %eax
leave
ret

So on my compiler and my platform for primitives, my earlier question is
answered in the affirmative (i.e. yes, using an = sign to assign an object
that is currently being initialized is OK and has no effect). Now, my real
question is "Is this the case everywhere?" Thanks!
 
D

Dietmar Kuehl

James said:
Foo x = Foo(Bar());

My question is, what exactly does this do? Do is create a default
constructed Foo and then overwrite it using the assignment operator with
whatever is on the right side?
No.

Or does it simply initialize a Foo object
with whatever is on the right side?

Not exactly. The above conceptually does the following in that
sequence:

- default construct a 'Bar' temporary
- construct a 'Foo' temporary with the 'Bar' temporary as parameter
- copy construct the 'Foo' variable 'x' from the 'Foo' temporary

However, the compiler is permitted to elide the 'Foo' temporary and
immediately construct the 'Foo' variable 'x'. Even if the compiler does
so,
it is required to check that 'Foo's copy constructor is accessible.
Can I count on this to happen the same way everywhere?
No.

Lastly, is there any way that this pattern of using
assignment can hurt me silently (i.e. performance-wise or any other
way)?

If 'Foo's copy constructor is relatively expensive, it may hurt you on
platforms or with compiler sittings where the temporary is not elided.
Sorry if the question seems obscure or useless. It's just that I plan on
doing this for a long time, and I'd like to know now how I need to proceed
so I don't shoot myself in the foot later. =)

A safer approach is to use the extra parenthesis:

Foo x((Bar()));

However, it is not only safer but also visually more obscure. Since
more is
better, you might want to go for more obscurity in this case :)
 
H

Howard

Dietmar Kuehl said:
Foo x((Bar()));

However, it is not only safer but also visually more obscure. Since
more is
better, you might want to go for more obscurity in this case :)

Personally, I prefer to be less obscure, and would do this instead:

Bar b;
Foo x(b);

This is clearer as to exactly what's going on. It also lets me examine the
Bar object (b) in the debugger easily. I dislike unnamed temporaries, and
the issue under discussion is just another reason why.
Just my 2 cents worth...
-H
 
D

Derek

Dietmar said:
Not exactly. The above conceptually does the following in that
sequence:

- default construct a 'Bar' temporary
- construct a 'Foo' temporary with the 'Bar' temporary as parameter
- copy construct the 'Foo' variable 'x' from the 'Foo' temporary ....

No.

Just so I understand, are you saying that if I write

Foo x = Foo(1,2,3);

the compiler may choose to elide the temporary and generate the
same code as if I had written

Foo x(1,2,3);

but that it is not required to?
 
D

Dietmar Kuehl

Derek said:
Just so I understand, are you saying that if I write

Foo x = Foo(1,2,3);

the compiler may choose to elide the temporary and generate the
same code as if I had written

Foo x(1,2,3);

but that it is not required to?

Right. The reason behind this is pretty simple: the semantic of
the initialization syntax is defined in terms of a general expression
like this:

T obj = expr;

The special case where 'expr' is actually a constructor call for a
'T' object is not specifically addressed. Thus, the behavior is as
if the expression 'expr' is executed first, followed by the
initialization. On the other hand, temporaries can always be elided
but there is no guarantee that they actually are elided.
 
A

Andrey Tarasevich

Derek said:
...
Just so I understand, are you saying that if I write

Foo x = Foo(1,2,3);

the compiler may choose to elide the temporary and generate the
same code as if I had written

Foo x(1,2,3);

but that it is not required to?

Yes, that's exactly correct.
 
M

Micah Cowan

Dietmar said:
Right. The reason behind this is pretty simple: the semantic of
the initialization syntax is defined in terms of a general expression
like this:

T obj = expr;

The special case where 'expr' is actually a constructor call for a
'T' object is not specifically addressed. Thus, the behavior is as
if the expression 'expr' is executed first, followed by the
initialization. On the other hand, temporaries can always be elided
but there is no guarantee that they actually are elided.

It seems to me, though, that if the result of constructing a
temporary of type Foo with int args and initializing a new Foo
with that object is determinably different from initializing the
new Foo directly with those args, then both constructions must
occur. Example:

-----

#include <iostream>

class Foo {
bool _initialized_by_another_foo;
int _one, _two, _three;

public:
Foo(const Foo &other)
{
_one = other._one;
_two = other._two;
_three = other._three;
_initialized_by_another_foo = true;
}

Foo(int one, int two, int three)
{
_one = one;
_two = two;
_three = three;
_initialized_by_another_foo = false;
}

bool was_copy_constructed() const
{
return _initialized_by_another_foo;
}
};

std::eek:stream &operator << (std::eek:stream &s, const Foo &f)
{
s << (f.was_copy_constructed() ? "true" : "false");
}

int main(void)
{
using std::cout;
using std::endl;

Foo a(1,2,3);
Foo b(a);
Foo c = Foo(1,2,3);

cout << a << endl << b << endl << c << endl;
}

-----

I was expecting the above program to output

false
true
true

but with g++ 3.2 (apparently a prerelease version of it), I get:

false
true
false

Meaning that I can definitely determine that c was not
constructed from another Foo.

I get the same results with:

Foo c(Foo(1,2,3));

Can anyone explain this to me? From what I've been able to find
in the standard, the program must behave as if the temp had been
constructed, producing "true" for the last line.
 
D

David Harmon

On Thu, 06 Jan 2005 12:28:02 -0800 in comp.lang.c++, Micah Cowan
It seems to me, though, that if the result of constructing a
temporary of type Foo with int args and initializing a new Foo
with that object is determinably different from initializing the
new Foo directly with those args, then both constructions must
occur.

No, the rule is that the temporary may be eliminated. It is up to
you to make sure that your program works correctly either way.
 
A

Andrey Tarasevich

Micah said:
...
I was expecting the above program to output

false
true
true

but with g++ 3.2 (apparently a prerelease version of it), I get:

false
true
false

Meaning that I can definitely determine that c was not
constructed from another Foo.

I get the same results with:

Foo c(Foo(1,2,3));

Can anyone explain this to me? From what I've been able to find
in the standard, the program must behave as if the temp had been
constructed, producing "true" for the last line.

One important detail about temporary elimination is that the standard
allows compilers to eliminate the intermediate temporary object
_regardless_ of any additional [side-]effects of the copy constructor of
the class. Indeed, you can determine which approach the compiler has
chosen in each particular case (by using the above technique, for
example), but that still doesn't mean that the behavior must be
consistent across compilers and even within one given compiler. In other
words, a well-written C++ program shall not depend on this behavior in
any essential way. Otherwise, the program's behavior wouldn't be specified.
 

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
474,197
Messages
2,571,038
Members
47,633
Latest member
BriannaLyk

Latest Threads

Top