views:

362

answers:

6

hello

I have code that looks like this:

class T {};

class container {
 const T &first, T &second;
 container(const T&first, const T & second);
};

class adapter : T {};

container(adapter(), adapter());

I thought lifetime of constant reference would be lifetime of container. However, it appears otherwise, adapter object is destroyed after container is created, leaving dangling reference.

What is the correct lifetime?

is stack scope of adapter temporary object the scope of container object or of container constructor?

how to correctly implement binding temporary object to class member reference?

Thanks

+3  A: 

Temporary const references only have the lifetime of the current statement (that is, they go out of scope just before the semi-colon). So the rule of thumb is never rely on a const-reference existing beyond the lifetime of the function that receives it as a parameter, in this case that's just the constructor. So once the constructor is done, don't rely on any const references to still be around.

There is no way to change/override/extend this lifetime for temporaries. If you want a longer lifetime, use an actual object and not a temporary:

adapter a, b; 
container(a, b); // lifetime is the lifetime of a and b

Or better yet, just don't use constant references to class members except in the most dire circumstances when the objects are very closely related and definitely not temporary.

SoapBox
To be more precise, they live until the end of the full expression they were created in.
GMan
"There is no way to change/override/extend this lifetime for temporaries" - actually there is, it's just not useful in cases like this. If you use a temporary to initialize a const reference with automatic duration, then the lifetime of the temporary is extended until the scope of the automatic is exited.
Steve Jessop
+1  A: 
Michael Aaron Safyan
the real objects are quite heavy with side effects constructors.I am trying to avoid copy construct.
aaa
@aaa, in that case, you should be using smart pointers such as boost::shared_ptr.
Michael Aaron Safyan
I thought of doing that, however class is in public interface which am trying to keep boost free
aaa
A: 

Don't do this. A temporary is destroyed immediately after the expression in which it was created (except in the case that it's immediately bound to a reference, in which case it's the scope of the reference). The lifetime cannot be extended to that of the class.

This is why I never store members as references - only copied objects or pointers. To me, pointers make it obvious that the lifetime comes in to play. Especially in the case of a constructor, it's non-obvious that your constructor params must outlive the class itself.

Stephen
-1: Pointers should be replaced with references whenever possible.
Billy ONeal
I didn't -1, but they live until the end of the full expression they were created in, not the scope.
GMan
First of all, that's a ridiculous statement. Second of all, in this case references make this behavior entirely non-obvious. Lame -1.
Stephen
GMan - the difference being in a case like "const string". In that case, it's bound to the scope of the reference.
Stephen
+1 : I disagree with pointer hate. In this case, pointers make it explicit that what's being referenced lives somewhere else in the ether and we have no idea whether it is valid or not. They also have the added feature of NULL/o/nullptr, which hopefully would allow us to ask the pointer if it was still valid (there's no way to ask that of a reference).
SoapBox
@Stephen: Yes. So entirely non obvious that it's the default behavior for every other major object oriented programming language (except objective C). Pointers are a C relic that can give you expressive power. But if you're not using that power, then the pointer needs to be replaced with the reference.
Billy ONeal
@Stephen: I'm replying to the text: "The lifetime of the temporary is the scope of the temporary." If you want to clarify that, please do, but it's not correct. (In the simplest case, which is what the text states.)
GMan
@Billy, that "the default behavior" is usually accompanied with garbage collection, so holding references to outdated objects is a non-issue.
Stephen
@Stephen: Considering the object in question should never be `delete` ing the pointer I fail to see how garbage collection has anything at all to do with references versus pointers.
Billy ONeal
@GMan - indeed, clarified.
Stephen
@Billy, because in the case of Object(Param(), OtherParam()), the references to the parameter objects don't last long enough in C++. It's non-obvious to the reader of the code that this is a bug. In Java/Python/etc the params are ref-counted and so they live long enough. We can agree to disagree on our opinion of ptrs/references, and agree to stay out of each-others code bases :)
Stephen
@Billy ONeal: not really, a lot of those other major OO languages have re-seatable, nullable references. Since C++'s references are not nullable or re-seatable, it doesn't make a whole lot of sense to say, "well, Java uses references therefore C++ code should use references". The references aren't the same. Anyway, using a pointer doesn't actually force you to do pointer arithmetic, and it's avoiding that which leads those other languages to avoid pointers. I note with interest that Go has pointers, but no pointer arithmetic, and no separate pointer-member-access operator.
Steve Jessop
@Steve Jessop: I'm not saying that someone should use references because that's how other languages do it. I'm just saying that references are not "entirely non-obvious".
Billy ONeal
@Billy: Nobody said that references are "entirely non-obvious". What was said was that taking a constructor param by reference and storing it as a member variable makes it non-obvious at the call-site that the reference needs to outlive the class.
Stephen
+1  A: 

I'm having a lot of difficulty understanding the question so please bear with me if I did not answer the way you expected.

I thought lifetime of constant reference would be lifetime of container. However, it appears otherwise, adapter object is destroyed after container is created, leaving dangling reference.
That is not correct. The object to which the reference points does not have some sort of magical way to say, "hey! there are still references to me!". A reference does absolutely nothing regarding the lifetime of an object in C++. If the object is destroyed, the reference is invalidated, and code that attempts to use the reference afterwards will invoke undefined behavior (usually a segmentation fault).

What is the correct lifetime?
We can't tell from the code you have posted. The lifetime of the object referenced will last the same amount of time as if it were not referenced.

is stack scope of adapter temporary object the scope of container object or of container constructor?
Neither. It's the scope of code in which the stack allocated object is declared. That is, the object will be destroyed when you reach the end of the code block in which the automatically allocated object is declared.

how to correctly implement binding temporary object to class member reference?
You can't. You can either copy the object or create a permanent object, but you can't bind a persistent reference to a temporary. The temporary is destroyed after execution of the statement that created it.

Billy ONeal
+9  A: 

According to the C++03 standard, a temporary bound to a reference has differing lifetimes depending on the context. In your example, I think the highlighted portion below applies (12.2/5 "Temporary objects"):

The temporary to which the reference is bound or the temporary that is the complete object to a subobject of which the temporary is bound persists for the lifetime of the reference except as specified below. A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits. A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full expression containing the call.

So while binding a temporary is an advanced technique to extend the lifetime of the temporary object (GotW #88: A Candidate For the "Most Important const"), it apparently won't help you in this case.

On the other hand, Eric Niebler has an article that you may be interested in that discusses an interesting (if convoluted) technique that could let your class's constructors deduce whether a temporary object (actually an rvalue) has been passed to it (and therefore would have to be copied) or a non-temporary (lvalue) as been passed (and therefore could potentially safely have a reference stashed away instead of copying):

Good luck with it though - every time I read the article, I have to work through everything as if I've never seen the material before. It only sticks with me for a fleeting moment...

And I should mention that C++0x's rvalue references should make Niebler's techniques unnecessary. Rvalue references will be supported by MSVC 2010 which is scheduled to be released in a week or so (on 12 April 2010 if I recall correctly). I don't know what the status of rvalue references is in GCC.

Michael Burr
I think actually in this case the temporary is bound to a function call parameter (the constructor call) as in the next sentence. Yes, it's also bound to the member because of the aliasing in the ctor initializer, and yes, it will persist until the constructor exits (longer, in fact, if the full-expression containing the constructor call also does other things). But I think the highlighted passage refers to stuff like `struct container { const container() : a(adapter()) {} };`.
Steve Jessop
@Steve: on closer look, I think you're right - I'll update the answer (same result though).
Michael Burr
A: 

If you want to avoid copying, then I suppose the Container must create the stored instances itself.

If you want to invoke the default constructor, then it should be no problem. Just invoke the default constructor of Container.

It is probably more problematic if you want to invoke a non-default constructor of the contained type. C++0x will have better solutions for that.

As an excercise, the container can accept a T, or an object containing the arguments for the constructor of T. This still relies on RVO (return value optimization).

template <class T1>
class construct_with_1
{
    T1 _1;
public:
    construct_with_1(const T1& t1): _1(t1) {}
    template <class U>
    U construct() const { return U(_1); }
};

template <class T1, class T2>
class construct_with_2
{
    T1 _1;
    T2 _2;
public:
    construct_with_2(const T1& t1, const T2& t2): _1(t1), _2(t2) {}
    template <class U>
    U construct() const { return U(_1, _2); }
};

//etc for other arities

template <class T1>
construct_with_1<T1> construct_with(const T1& t1)
{
    return construct_with_1<T1>(t1);
}

template <class T1, class T2>
construct_with_2<T1, T2> construct_with(const T1& t1, const T2& t2)
{
    return construct_with_2<T1, T2>(t1, t2);
}

//etc
template <class T>
T construct(const T& source) { return source; }

template <class T, class T1>
T construct(const construct_with_1<T1>& args)
{
    return args.template construct<T>();
}

template <class T, class T1, class T2>
T construct(const construct_with_2<T1, T2>& args)
{
    return args.template construct<T>();
}

template <class T>
class Container
{
public:
    T first, second;

    template <class T1, class T2>
    Container(const T1& a = T1(), const T2& b = T2()) : 
        first(construct<T>(a)), second(construct<T>(b)) {}
}; 

#include <iostream>

class Test
{
    int n;
    double d;
public:
    Test(int a, double b = 0.0): n(a), d(b) { std::cout << "Test(" << a << ", " << b << ")\n"; }
    Test(const Test& x): n(x.n), d(x.d) { std::cout << "Test(const Test&)\n"; }
    void foo() const { std::cout << "Test.foo(" << n << ", " << d << ")\n"; }
};

int main()
{
    Test test(4, 3.14);
    Container<Test> a(construct_with(1), test); //first constructed internally, second copied
    a.first.foo();
    a.second.foo();
}
visitor