By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
458,052 Members | 1,235 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 458,052 IT Pros & Developers. It's quick & easy.

lifetime of temporaries

P: n/a
Rather a long program I'm afraid but I don't think I can cut it down any
further.

What I'm trying to do is construct a complex object Y from several X objects
in a complex expression. I'm trying to do this without creating any
temporaries of Y. To do that I'm defined a number of proxy classes which
contain references to the arguments in the expression. All the proxies
define a conversion operator to Y which will ultimately be used to create
the Y object. The issue for me is whether I'm guaranteed that all the
temporaries will still exist when the conversion happens. Here's the code

#include <iostream>
using namespace std;

int gen = 0;

class X
{
};

class Y
{
};

class Proxy
{
public:
Proxy() : id(gen++) {}
Proxy(const Proxy&) : id(gen++) {}
virtual ~Proxy() {}
operator Y()
{
cout << "convert\n";
return Y();
}
protected:
int id;
};

class XX_proxy : public Proxy
{
public:
XX_proxy(const X& l, const X& r) : left(l), right(r) { cout << "XX" << id
<< '\n'; }
~XX_proxy() { cout << "~XX" << id << '\n'; }
private:
const X& left;
const X& right;
};

class AX_proxy : public Proxy
{
public:
AX_proxy(const Proxy& l, const X& r) : left(l), right(r) { cout << "AX" <<
id << '\n'; }
~AX_proxy() { cout << "~AX" << id << '\n'; }
private:
const Proxy& left;
const X& right;
};

class XA_proxy : public Proxy
{
public:
XA_proxy(const X& l, const Proxy& r) : left(l), right(r) { cout << "XA" <<
id << '\n'; }
~XA_proxy() { cout << "~XA" << id << '\n'; }
private:
const X& left;
const Proxy& right;
};

class AA_proxy : public Proxy
{
public:
AA_proxy(const Proxy& l, const Proxy& r) : left(l), right(r) { cout << "AA"
<< id << '\n'; }
~AA_proxy() { cout << "~AA" << id << '\n'; }
private:
const Proxy& left;
const Proxy& right;
};

XX_proxy operator|(const X& l, const X& r)
{
return XX_proxy(l, r);
}

AX_proxy operator|(const Proxy& l, const X& r)
{
return AX_proxy(l, r);
}

XA_proxy operator|(const X& l, const Proxy& r)
{
return XA_proxy(l, r);
}

AA_proxy operator|(const Proxy& l, const Proxy& r)
{
return AA_proxy(l, r);
}

int main()
{
X x1, x2, x3, x4;
Y y = x1 | x2 | x3 | x4;
}

The following program outputs

XX0
AX1
AX2
convert
~AX2
~AX1
~XX0

on the two compilers I've tested it on, which is good, "convert" is output
before any of the destructors are called. But I'm not sure if this is
guaranteed behaviour. I've tried reading the standard but its a bit opaque
to me.

Thanks,
John
Jul 22 '05 #1
Share this Question
Share on Google+
13 Replies


P: n/a
John Harrison wrote in news:c6************@ID-196037.news.uni-berlin.de
in comp.lang.c++:

What I'm trying to do is construct a complex object Y from several X
objects in a complex expression. I'm trying to do this without
creating any temporaries of Y. To do that I'm defined a number of
proxy classes which contain references to the arguments in the
expression. All the proxies define a conversion operator to Y which
will ultimately be used to create the Y object. The issue for me is
whether I'm guaranteed that all the temporaries will still exist when
the conversion happens. Here's the code

[snip expression template (without the template) example]

The temporaries are destroyed at the final sequence point of the
full expresion, that's the ';', after the initialization of Y y,
so your code is ok.

What will happen when/if we get the auto extension and can rewrite:
int main()
{
X x1, x2, x3, x 4;
Y y = x1 | x2 | x3 | x4;
}


as

int main()
{
X x1, x2, x3, x 4;

auto e1 = x1 | x2;
auto e2 = x3 | x4;

Y y = e1 | e2;
}

Is another matter. I've a feeling it just won't scale :),
though the above example should be ok.

Rob.
--
http://www.victim-prime.dsl.pipex.com/
Jul 22 '05 #2

P: n/a
Rob Williscroft wrote:

What will happen when/if we get the auto extension and can rewrite:

int main()
{
X x1, x2, x3, x 4;
Y y = x1 | x2 | x3 | x4;
}

as

int main()
{
X x1, x2, x3, x 4;

auto e1 = x1 | x2;
auto e2 = x3 | x4;

Y y = e1 | e2;
}

Is another matter. I've a feeling it just won't scale :),
though the above example should be ok.


According to the C++ standard the lifecycle of a temporary may be even
longer than the end of the full-expression when it is used in an
expression initializing an object or is assigned to a reference.

I think it might be quite hard to write code that bites with a
disappearing temporary as far as a standard conforming compiler is used.
Regards,
Janusz
Jul 22 '05 #3

P: n/a

"Janusz Szpilewski" <sz******@poczta.onet.pl> wrote in message
news:c6**********@news.onet.pl...
Rob Williscroft wrote:

What will happen when/if we get the auto extension and can rewrite:

int main()
{
X x1, x2, x3, x 4;
Y y = x1 | x2 | x3 | x4;
}

as

int main()
{
X x1, x2, x3, x 4;

auto e1 = x1 | x2;
auto e2 = x3 | x4;

Y y = e1 | e2;
}

Is another matter. I've a feeling it just won't scale :),
though the above example should be ok.


According to the C++ standard the lifecycle of a temporary may be even
longer than the end of the full-expression when it is used in an
expression initializing an object or is assigned to a reference.

I think it might be quite hard to write code that bites with a
disappearing temporary as far as a standard conforming compiler is used.
Regards,
Janusz


I managed it with my first attempt at this. That had code similar to this

class X {};
class Y { Y(const X& a) : arg(a) {} const X& arg; };
class Z { Z(const Y& l, const Y& r) : left(l), right(r) {} const Y& left;
const Y& right; };

Z func(const X& l, const X& r)
{
return Z(Y(l), Y(r));
}

X x1, x2;
Z z = func(x1, x2);

The Y temporaries got destroyed while the Z object still existed.

john
Jul 22 '05 #4

P: n/a
Janusz Szpilewski wrote in news:c6**********@news.onet.pl in comp.lang.c++:
Rob Williscroft wrote:

What will happen when/if we get the auto extension and can rewrite:

int main()
{
X x1, x2, x3, x 4;
Y y = x1 | x2 | x3 | x4;
}

as

int main()
{
X x1, x2, x3, x 4;

auto e1 = x1 | x2;
auto e2 = x3 | x4;

Y y = e1 | e2;
}

Is another matter. I've a feeling it just won't scale :),
though the above example should be ok.


According to the C++ standard the lifecycle of a temporary may be even
longer than the end of the full-expression when it is used in an
expression initializing an object or is assigned to a reference.

I think it might be quite hard to write code that bites with a
disappearing temporary as far as a standard conforming compiler
is used.


I wish you were right:

#include <iostream>
#include <ostream>

using namespace std;

struct X
{
X() { cout << "X()\n"; }
X( X const & ) { cout << "X(X const &)\n"; }
~X() { cout << "~X()\n"; }
};
struct Y
{
Y( X const &x ) : xp( &x ) { cout << "Y()\n"; }
Y( Y const &rhs ) : xp( rhs.xp ) { cout << "Y(Y const &)\n"; }
~Y() { cout << "~Y()\n"; }

X const *xp;
};
void f()
{
Y y( (X()) );

cout << "f()\n";
}

int main()
{
f();
cout.flush();
}
Every compiler I tried gave:

X()
Y()
~X()
f()
~Y()

So either they are all wrong (*), or that "...initializing an object..."
stuff isn't about keeping the X() from Y y( (X()) ); around.

*) Not impossible, compilers were:

MSVC 7.1, g++ 3.4 (prerelease) and CBuildeX (preview/EDG))

Consider:

X x1 = X(), x2 = X();

Note that the first X() gets consumed ( in effect becomes x1 )
by copy-ctor elision, so doesn't survive to the ';', but this is
2 initialization's, is it 1 (full) expression ?

So I really don't get that "initializing an object" stuff. All
I can think is it means when a temporary becomes the object
its initializing, so in effect the X() initializing x1 above
becomes x1, and thus 'lives' beyond the full expression.

Rob.
--
http://www.victim-prime.dsl.pipex.com/
Jul 22 '05 #5

P: n/a
Rob Williscroft wrote:

So I really don't get that "initializing an object" stuff. All
I can think is it means when a temporary becomes the object
its initializing, so in effect the X() initializing x1 above
becomes x1, and thus 'lives' beyond the full expression.


Some qoute from the standard (draft version):

" (..) The first context is when an expression appears as an initializer
for a declarator defining an object. In that context, the temporary
that holds the result of the expression shall persist until the object's
initialization is complete. "

It means that the temporary should be destroyed after the object
initialization, hence just after leaving the constructor. So it does not
have to live as long as the constructed object.

Regards,
Janusz
Jul 22 '05 #6

P: n/a
John Harrison wrote:
I managed it with my first attempt at this. That had code similar to this

class X {};
class Y { Y(const X& a) : arg(a) {} const X& arg; };
class Z { Z(const Y& l, const Y& r) : left(l), right(r) {} const Y& left;
const Y& right; };

Z func(const X& l, const X& r)
{
return Z(Y(l), Y(r));
}

X x1, x2;
Z z = func(x1, x2);

The Y temporaries got destroyed while the Z object still existed.


Yes, if a temporary is bound to a reference member it will persist only
until constructor ends.

Jul 22 '05 #7

P: n/a

"Janusz Szpilewski" <sz******@poczta.onet.pl> wrote in message
news:c6**********@news.onet.pl...
John Harrison wrote:
I managed it with my first attempt at this. That had code similar to this
class X {};
class Y { Y(const X& a) : arg(a) {} const X& arg; };
class Z { Z(const Y& l, const Y& r) : left(l), right(r) {} const Y& left; const Y& right; };

Z func(const X& l, const X& r)
{
return Z(Y(l), Y(r));
}

X x1, x2;
Z z = func(x1, x2);

The Y temporaries got destroyed while the Z object still existed.


Yes, if a temporary is bound to a reference member it will persist only
until constructor ends.


But doesn't that apply to my other code as well? In that code I created an
XX_proxy object which was bound to a reference member (in a AX_proxy object)
yet that persisted beyond the end of the AX_proxy constructor. This is
essentially the point that confused me when I tried to read the standard.

John
Jul 22 '05 #8

P: n/a
John Harrison wrote:
"Janusz Szpilewski" <sz******@poczta.onet.pl> wrote in message
news:c6**********@news.onet.pl...
John Harrison wrote:

I managed it with my first attempt at this. That had code similar to
this
class X {};
class Y { Y(const X& a) : arg(a) {} const X& arg; };
class Z { Z(const Y& l, const Y& r) : left(l), right(r) {} const Y&
left;
const Y& right; };

Z func(const X& l, const X& r)
{
return Z(Y(l), Y(r));
}

X x1, x2;
Z z = func(x1, x2);

The Y temporaries got destroyed while the Z object still existed.


Yes, if a temporary is bound to a reference member it will persist only
until constructor ends.

But doesn't that apply to my other code as well? In that code I created an
XX_proxy object which was bound to a reference member (in a AX_proxy object)
yet that persisted beyond the end of the AX_proxy constructor. This is
essentially the point that confused me when I tried to read the standard.

John


In the code posted previously the temporaries primarily existed within
the context of the full-expression where they were created (y = x1 | x2
| x3 | x4). In the latter case when 'func' returns the Z member
reference seems to be the only contexts of the existence of the
temporary what is not enough after the completion of the object
construction.

Regards,
Janusz
Jul 22 '05 #9

P: n/a
Janusz Szpilewski wrote in news:c6**********@news.onet.pl in comp.lang.c++:
Rob Williscroft wrote:

So I really don't get that "initializing an object" stuff. All
I can think is it means when a temporary becomes the object
its initializing, so in effect the X() initializing x1 above
becomes x1, and thus 'lives' beyond the full expression.

Some qoute from the standard (draft version):

" (..) The first context is when an expression appears as an initializer
for a declarator defining an object. In that context, the temporary
that holds the result of the expression shall persist until the object's
initialization is complete. "

It means that the temporary should be destroyed after the object
initialization, hence just after leaving the constructor. So it does not
have to live as long as the constructed object.


Yup, but that doesn't explain how, due to a temporary being
used in an intialization, the temporary can persist beyond the
full expression.

However I just searched the current Standard for 'temporary'
and I couldn't find a reference to:
According to the C++ standard the lifecycle of a temporary may be even
longer than the end of the full-expression when it is used in an
expression initializing an object or is assigned to a reference.


or similar, except for binding to T const & and in throw expression's.

So it seems my memory is at fault and temporaries are destroyed at
the end of "the full expression", except in the two cases noted
above.

Rob.
--
http://www.victim-prime.dsl.pipex.com/
Jul 22 '05 #10

P: n/a
"John Harrison" <jo*************@hotmail.com> wrote in message news:<c6************@ID-196037.news.uni-berlin.de>...
Rather a long program I'm afraid but I don't think I can cut it down any
further.

*snip*
Y y = x1 | x2 | x3 | x4;


Well, fortunately the answer is short .. <grin> Yes, the temporaries
returned by the operator| calls are guaranteed to live until the end
of the full expression where they are generated .. in this case the
assignment expression quoted above.

Dave Moore
Jul 22 '05 #11

P: n/a
Rob Williscroft wrote:

Yup, but that doesn't explain how, due to a temporary being
used in an intialization, the temporary can persist beyond the
full expression.

However I just searched the current Standard for 'temporary'
and I couldn't find a reference to:

According to the C++ standard the lifecycle of a temporary may be even
longer than the end of the full-expression when it is used in an
expression initializing an object or is assigned to a reference.

or similar, except for binding to T const & and in throw expression's.

So it seems my memory is at fault and temporaries are destroyed at
the end of "the full expression", except in the two cases noted
above.


When I wrote "longer" I primarily kept in mind the case of binding
temporary to a reference. Object initialization concerns more formal
issue as the formal name of an construct defining an object (containing
a type name) is called declarator and it covers more than just an
expression (which represents the initializer part). So the standard had
just to clarify this issue.

Regards,
Janusz

Jul 22 '05 #12

P: n/a
"John Harrison" <jo*************@hotmail.com> wrote in message
news:c6************@ID-196037.news.uni-berlin.de...
Rather a long program I'm afraid but I don't think I can cut it down any
further.

What I'm trying to do is construct a complex object Y from several X objects in a complex expression. I'm trying to do this without creating any
temporaries of Y. To do that I'm defined a number of proxy classes which
contain references to the arguments in the expression.


[snip]

I hope your objective of avoiding temporaries is worth writing code which
does so little with so much complexity. To put it more bluntly, I can hardly
think of a practical application where the increased efficiency would
justify such obscure code.

Still, it would be nice to be able to control creation and destruction of
temporaries. Maybe it's a problem with the language.

--
Cy
http://home.rochester.rr.com/cyhome/
Jul 22 '05 #13

P: n/a
"John Harrison" <jo*************@hotmail.com> wrote in message news:<c6************@ID-196037.news.uni-berlin.de>...

I managed it with my first attempt at this. That had code similar to this

class X {};
class Y { Y(const X& a) : arg(a) {} const X& arg; };
class Z { Z(const Y& l, const Y& r) : left(l), right(r) {} const Y& left;
const Y& right; };

Z func(const X& l, const X& r)
{
return Z(Y(l), Y(r));
}

X x1, x2;
Z z = func(x1, x2);

The Y temporaries got destroyed while the Z object still existed.


I think that's special because RVO should be applied here:
there is only one Z object with lifetime geater than func().
It's like saying

X x1, x2;
Z z (Y(x1), Y(x2));

where, too, the Y temporaries get destroyed while the Z still exists.

Gerald
Jul 22 '05 #14

This discussion thread is closed

Replies have been disabled for this discussion.