Sign in to follow this  
muse1987

[SOLVED] Question on C++ temporary objects

Recommended Posts

Hi I have a class which overloads operator + (). Until now it has worked fine, but I don't know whether it's legal or relies on nonstandard behavior. The operator + () function basically stores address of two operands to another struct and returns it. Here's the code
struct Foo;

struct Sum
{
    const Foo *p1, *p2;

    Sum( const Foo* p1, const Foo* p2 )
        : p1(p1), p2(p2) {}
};

struct Foo
{
    int value;

    Foo( int val )
        : value(val) {}

    Sum operator + ( const Foo& Other ) const
        { return Sum( this, &Other ); }
};





And the usage
void func( const Sum& x )
{
    cout << x.p1->value << ' ' << x.p2->value << endl;
}

int main()
{
    func( Foo(100) + Foo(200) );
}





Probably you'll see what I'm worring about. In the main() function, if temporary objects created by Foo(100) and Foo(200) are destructed before calling func() function, the code will not work. Fortunately in Visual C++ the temporary objects are destructed after func() function is finished. But I want to know if this behavior is guaranteed by the standard. Code for main() function generated by Visual C++ looks like this:
int main()
{
    {
        Foo temp1( 100 );
        Foo temp2( 200 );
        Sum temp3( temp1.operator + ( temp2 ) );
        func( temp3 );
    } // temp1, temp2, and temp3 are all destructed here
}





What I'm worring is if the compiler generate this way
int main()
{
    {
        Sum temp3;

        {
            Foo temp1(100);
            Foo temp2(200);
            temp3 = temp1.operator + ( temp2 );
        } // temp1 and temp2 are destructed here

        // now temp3.p1 and temp3.p2 are garbage, as temp1 and temp2 are destructed already.

        func( temp3 );
    } // temp3 is destructed here
}





Does the standard guarantee temp1 and temp2 not destructed until func() function is finished? Thanks in advance [Edited by - muse1987 on September 16, 2008 10:19:15 PM]

Share this post


Link to post
Share on other sites
The lifetime of a temporary object can be extended by assigning it to a const-reference, and only a const-reference. If you change your const Foo pointers into const Foo references, then it should be guaranteed safe. Right now I believe it isn't guaranteed to be safe.
Having said that, I've read that the digital mars compiler compiler didn't follow the standard on this rule at some point, and produced code that wasn't compliant. Recent versions are of course fine though.
You're essentially taking the approach used by "expresion templates" in case that gives you something useful to google for.

Share this post


Link to post
Share on other sites
Thanks a lot for your reply iMalc. Now I understand what a expression template is.
However I have some questions

1) I need only '+' operator but after I concatenate several elements I need to access them as array. Here's my original code

struct iTextSource
{
virtual int getLength() = 0;

virtual void get( wchar_t* pBuffer ) = 0;
};

struct sTextSourceList
{
const iTextSource* const* ppList;
int nLength;

sTextSourceList( const iTextSource* const* ppList, int nLength )
: ppList(ppList), nLength(nLength) {}
};

template <int length>
struct text_source_list
{
const iTextSource* apList[length];

// operator + ()
// appends one element to produce text_source_list<length+1>
text_source_list<length+1>
operator + ( const iTextSource& elem ) const
{
text_source_list<length+1> result;
std::copy( apList, apList+length, result.apList );
result.apList[length] = &elem;
return result;
}

operator sTextSourceList() const
{ return sTextSourceList( apList, length ); }
};

template <>
struct text_source_list<0>
{
text_source_list<1>
operator + ( const iTextSource& elem ) const
{
text_source_list<1> result;
result.apList[0] = &elem;
return result;
}

operator sTextSourceList() const
{ return sTextSourceList( NULL, 0 ); }
};





And the usage:

std::wstring my_printf( const wchar_t* pFormat, const sTextSourceList& Args )
{
// some stuff
}

int main()
{
int x = 100, y = 200;

std::wstring str = my_printf(
L"${1} ${0}",
text_source_list<0>()
+ int_formatter(x) // int_formatter is derived from iTextSource
+ int_formatter(y)
);
}





So what could I do? Array of references :O ? Any suggestions?

2)
Quote:
The lifetime of a temporary object can be extended by assigning it to a const-reference

In the code of my first post, operator + () function of Foo class takes const reference, so wouldn't it be kept safe? What I'm wondering is if we seperate the code to multiple files, like this:


// foo.h
struct Foo;

struct Sum
{
const Foo *p1, *p2;
Sum( const Foo* p1, const Foo* p2 );
};

struct Foo
{
int value;

Foo( int val );
Sum operator + ( const Foo& Other ) const;
};






// foo.cpp
#include "foo.h"

Sum::Sum( const Foo* p1, const Foo* p2 )
: p1(p1), p2(p2)
{
}

Foo::Foo( int val )
: value(val)
{
}

Sum Foo::operator + ( const Foo& Other ) const
{
return Sum( this, &Other );
}






// main.cpp
#include "foo.h"

void func( const Sum& x )
{
cout << x.p1->value << ' ' << x.p2->value << endl;
}

int main()
{
func( Foo(100) + Foo(200) );
}




When the compiler compiles main.cpp, it doesn't know anything about operator + (), so it shouldn't know whether temporary objects are assigned to const reference. So what will happen in this case? I'm getting confused. :S

Would you give me answer?
Thanks

By the way, the reason why I'm doing this is related to multilanguage support. I need to specify index of argument in the format string. Like
printf( "${0} ${1}", 100, 200 ); -- prints "100 200"
printf( "${1} ${0}", 100, 200 ); -- prints "200 100"

[Edited by - muse1987 on September 16, 2008 9:02:19 PM]

Share this post


Link to post
Share on other sites
Quote:
By the way, the reason why I'm doing this is related to multilanguage support. I need to specify index of argument in the format string. Like
printf( "${0} ${1}", 100, 200 ); -- prints "100 200"
printf( "${1} ${0}", 100, 200 ); -- prints "200 100"


Why not use boost::format ?

#include <boost/format.hpp>
int main() {
std::cout << boost::format( "%1% %2%" ) % 100 % 200 << std::endl; // prints "100 200"
std::cout << boost::format( "%2% %1%" ) % 100 % 200 << std::endl; // prints "200 100"
}




boost website

Share this post


Link to post
Share on other sites
Quote:
Original post by iMalc
Right now I believe it isn't guaranteed to be safe.

It should be, actually.

Quote:
12.2.3

When an implementation introduces a temporary object of a class that has a non-trivial constructor (12.1), it
shall ensure that a constructor is called for the temporary object. Similarly, the destructor shall be called for
a temporary with a non-trivial destructor (12.4). Temporary objects are destroyed as the last step in evaluating
the full-expression (1.9) that (lexically) contains the point where they were created. This is true even
if that evaluation ends in throwing an exception.


In other words, the temporaries will last until the terminating semicolon of the func(...); line is reached, so to speak.

Share this post


Link to post
Share on other sites
Okay, so the full-expression is the whole line then. I guess I was wrongly assuming it was just the operator+ call. Of course I'd still recommend const-references anyway, and the case I spoke of does still exist in some situation.

muse1987: Arrays of references are illegal in C++ afaik.

Share this post


Link to post
Share on other sites
Like MaulingMonkey said: your wheel exists and is named boost::format.

But if you really had to do it yourself...

1) You don't really need or want a template on length... you're only doing this in order to be able to count things up as you add them, with some slightly advanced template trickery. So why not just use a container that already knows how to count its elements?

2) If you're worried about things going out of scope, the C++ idiom is to just make your own copy. Which containers generally do.

3) If you're using std::wstring, why not use it consistently?

4) Instead of making derived classes to handle text formatting, can't you just use a function?


typedef std::vector<std::wstring> text_list;

std::wstring my_printf(const std::wstring& format, const text_list& Args) {
// some stuff
}

std::wstring int_formatter(int x) {
// Or you could, you know, use boost::lexical_cast ;)
}

int main() {
int x = 100, y = 200;

std::wstring str = my_printf(
L"${1} ${0}",
text_list()
.push_back(int_formatter(x))
.push_back(int_formatter(y))
);
}

Share this post


Link to post
Share on other sites
Thanks for your informations. I'm happy now. :)

By the way, what my boss wants me to do is pretty complicated.
1) The my_printf() (will be renamed once I finish) should not be too difficult to use compared to sprintf() library function.
2) Only one dynamic memory allocation is allowed per one function call. Which means, it has to calculate result length first, allocate buffer, and do the formatting. Because of this, I couldn't use containers for parameter list as they involve heap operations.
3) It should be portable to any compiler which conforms current standard. If portability issue arises later on and it's dependency of nonstandard behavior, I shall be responsible for all changes required, including others' code which uses this function.

Well, I think my code satisfies all of his requirements. I've changed my_printf() function to take one reference to std::wstring, instead of return value.

Again, thanks a lot for your informations and efforts. I could learn a lot.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this