My question (which will follow after this, sorry about the long intro, the question is down there in bold) is originally inspired by Item 23 in Herb Sutters Exceptional C++ where we find something like this:
<snip>
...
int main()
{
GenericTableAlgorithm a( "Customer", MyWorker() );
a.Process();
}
with
class GenericTableAlgorithm
{
public:
GenericTableAlgorithm( const string& table,
GTAClient& worker );
bool Process();
private:
struct GenericTableAlgorithmImpl* pimpl_; //implementation
};
class GTAClient
{
///...
virtual bool ProcessRow( const PrimaryKey& ) =0;
//...
};
class MyWorker : public GTAClient
{
// ... override Filter() and ProcessRow() to
// implement a specific operation ...
};
</snip>
Now, I have the following problems with that code (and no, I in no way doubt Mr. Sutter's prowess as a C++ expert):
- The example like that will not work, since GTAClient& worker is a non-const reference which can't take a temporary, but well, it might have been written pre-standard or a typo, whatever, that's not my point.
- What makes me wonder is what he is going to do with the worker reference, even if Problem 1. is ignored.
Obviously the intention is to haveMyWorker
used in the NVI ofGenericTableAlgorithm
accessed byGTAClient
(polymorphic) interface; this rules out that the implementation owns a (value)member of typeGTAClient
, since that would cause slicing etc. value-semantics don't mix well with polymorphism.
It cannot have a data member of typeMyWorker
either since that class is unknown toGenericTableAlgorithm
.
So I conclude it must have been meant to be used via pointer or reference, preserving the original object and polymorphic nature. - Since pointers to temporary objects (
MyWorker()
) are rarely a good idea, i assume the author's plan was to use the extended life-time of temporaries bound to (const) references, and store such a reference in the objectpimpl_
points to and use it from there. (Note: there is also no clone-member function in GTAClient, which could have made this work; let's not assume there is a RTTI-typeinfo-based Factory lurking in the background.)
And here (finally!) my question sets in:(How) can passing a temporary to to a class' reference member with extended life-time be done legally ?
The standard in §12.2.5(the C++0x version but it's the same in C++, don't know about the chapter number) makes the following exception from lifetime extension:
"-A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits."
Therefore the object cannot be used in the call of the client code a.Process(); because the referenced temporary from MyWorker()
is already dead!
Consider now an example of my own crafting that demonstrates the problem (tested on GCC4.2):
#include <iostream>
using std::cout;
using std::endl;
struct oogie {
~oogie()
{
cout << "~oogie():" << this << ":" << m_i << endl;
}
oogie(int i_)
: m_i(i_)
{
cout << "oogie():" << this << ":" << m_i << endl;
}
void call() const
{
cout << "call(): " << this << ":" << m_i << endl;
}
int m_i;
};
oogie func(int i_=100)
{
return oogie(i_);
}
struct kangoo
{
kangoo(const oogie& o_)
: m_o(o_)
{
}
const oogie& m_o;
};
int main(int c_, char ** v_)
{
//works as intended
const oogie& ref = func(400);
//kablewy machine
kangoo s(func(1000));
cout << ref.m_i << endl;
//kangoo's referenced oogie is already gone
cout << s.m_o.m_i << endl;
//OK, ref still alive
ref.call();
//call on invalid object
s.m_o.call();
return 0;
}
which produces the output
oogie():0x7fff5fbff780:400 oogie():0x7fff5fbff770:1000 ~oogie():0x7fff5fbff770:1000 400 1000 call(): 0x7fff5fbff780:400 call(): 0x7fff5fbff770:1000 ~oogie():0x7fff5fbff780:400
You can see that in the case of const oogie& ref the immediately bound-to-reference temporary return value of func() has the extended lifetime of said reference (until end of main), so it's OK.
BUT: The 1000-oogie object is already destroyed right after kangoo-s was constructed. The code works, but we are dealing with an undead object here...
So to pose the question again:
Firstly, Am I missing something here and the code is correct/legal?.
Secondly, why does GCC give me no warning of this, even with -Wall specified? Should it? Could it?
Thanks for your time,
martin