C++ constant reference lifetime (container adaptor)

前端 未结 5 1344
花落未央
花落未央 2020-11-28 12:54

I have code that looks like this:

class T {};

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


        
相关标签:
5条回答
  • 2020-11-28 13:23

    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):

    • Conditional Love: FOREACH Redux

    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.

    0 讨论(0)
  • 2020-11-28 13:26

    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.

    0 讨论(0)
  • 2020-11-28 13:28

    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.

    0 讨论(0)
  • 2020-11-28 13:30

    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();
    }
    
    0 讨论(0)
  • 2020-11-28 13:31

    The reference will exist for the entire lifetime of container, but the object being referenced will exist only for the lifetime of that object. In this case, you have bound your reference to a temporary object with automatic storage allocation ("stack allocation", if you will, although that isn't C++ nomenclature). Therefore, you cannot expect the temporary to exist beyond the statement in which it was written (as it goes out of scope immediately after the call to the constructor for container). The best way to deal with this is to use a copy, instead of a reference. Since you are using a const reference, anyway, it will have similar semantics.

    You should redefine your class as:

    template<typename T> 
    class container 
    {
        public:
            container(const T& first, const T& second) : first(first), second(second) {}
        private:
            const T first;
            const T second;
    };
    

    Alternatively, you could give your objects a name to prevent them from going out of scope:

       adaptor first;
       adaptor second;
       container c(first,second);
    

    However, I don't think this a good idea, since a statement such as return c is invalid.

    Edit
    If your goal is to share objects in order to avoid the cost of copying, then you should consider using smart pointer objects. For example, we can redefine your object using smart pointers as follows:

    template<typename T> 
    class container 
    {
        public:
            container(const boost::shared_ptr<const T>& first, const boost::shared_ptr<const T>& second) : first(first), second(second) {}
        private:
            boost::shared_ptr<const T> first;
            boost::shared_ptr<const T> second;
    };
    

    You can then use:

    boost::shared_ptr<const adaptor> first(new adaptor);
    boost::shared_ptr<const adaptor> second(new adaptor);
    container<adaptor> c(first,second);
    

    Or, if you want to have mutable copies of first and second locally:

    boost::shared_ptr<adaptor> first(new adaptor);
    boost::shared_ptr<adaptor> second(new adaptor);
    container<adaptor> c(boost::const_pointer_cast<const adaptor>(first),boost::const_pointer_cast<const adaptor>(second));
    
    0 讨论(0)
提交回复
热议问题