C ++ const引用生命周期(容器适配器)

时间:2010-04-08 23:51:12

标签: c++ reference const lifetime

我的代码如下:

class T {};

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

class adapter : T {};

container(adapter(), adapter());

我认为不断引用的生命周期将是容器的生命周期。 但是,如果出现这种情况,则在创建容器后销毁适配器对象,留下悬空引用。

正确的生命周期是什么?

是适配器临时对象的堆栈范围,是容器对象或容器构造函数的范围吗?

如何正确实现绑定临时对象到类成员引用?

由于

5 个答案:

答案 0 :(得分:17)

根据C ++ 03标准,根据上下文,对引用的临时绑定具有不同的生命周期。在您的示例中,我认为下面突出显示的部分适用(12.2 / 5“临时对象”):

  

引用绑定的临时对象或作为临时绑定的子对象的完整对象的临时对象在引用的生命周期内持续存在,除非下面指定。绑定到构造函数的ctor-initializer(12.6.2)中的引用成员的临时绑定将持续存在,直到构造函数退出。 函数调用(5.2.2)中的引用参数的临时绑定将持续到包含该调用的完整表达式完成为止。

因此,绑定临时是一种延长临时对象(GotW #88: A Candidate For the "Most Important const")生命周期的高级技术,在这种情况下显然无法帮助你。

另一方面,Eric Niebler有一篇你可能感兴趣的文章讨论了一个有趣的(如果是复杂的)技术,可以让你的类的构造函数推断出临时对象(实际上是一个右值)是否已经传递给它(因此必须被复制)或非临时(左值)已被传递(因此可能安全地将参考藏匿而不是复制):

祝你好运 - 每当我读到这篇文章时,我都必须完成所有事情,好像我以前从未见过这些材料一样。它只会跟我一起转瞬即逝......

我应该提到C ++ 0x的右值引用应该使Niebler的技术变得不必要。 MSVC 2010将支持Rvalue引用,计划在一周左右发布(如果我没记错的话,将于2010年4月12日发布)。我不知道在GCC中rvalue引用的状态是什么。

答案 1 :(得分:6)

临时const引用只有当前语句的生命周期(也就是说,它们在分号之前就超出了范围)。因此,经验法则永远不会依赖于在接收它作为参数的函数的生命周期之外存在的const-reference,在这种情况下,它只是构造函数。所以一旦构造函数完成,就不要依赖于任何const引用仍然存在。

对于临时工具,无法更改/覆盖/延长此生命周期。如果您想要更长的生命周期,请使用实际对象而不是临时对象:

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

或者更好的是,只是不要对类成员使用常量引用,除非在最严峻的情况下,对象非常密切相关且绝对不是临时的。

答案 2 :(得分:1)

container的整个生命周期将存在引用,但被引用的对象仅在该对象的生命周期内存在。在这种情况下,您已将对具有自动存储分配的临时对象的引用绑定(“堆栈分配”,如果您愿意,尽管这不是C ++命名法)。因此,您不能指望临时存在于写入它的语句之外(因为它在调用container的构造函数之后立即超出范围)。处理此问题的最佳方法是使用副本而不是引用。因为你使用const引用,所以它将具有类似的语义。

您应该将您的课程重新定义为:

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

或者,您可以为对象指定一个名称,以防止它们超出范围:

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

但是,我认为这不是一个好主意,因为return c之类的陈述无效。

修改
如果您的目标是共享对象以避免复制成本,那么您应该考虑使用智能指针对象。例如,我们可以使用智能指针重新定义您的对象,如下所示:

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;
};

然后您可以使用:

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

或者,如果您想在本地拥有第一个和第二个的可变副本:

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

答案 3 :(得分:0)

如果你想避免复制,那么我认为Container必须自己创建存储的实例。

如果要调用默认构造函数,那么应该没问题。只需调用Container的默认构造函数。

如果要调用包含类型的非默认构造函数,则可能会出现问题。 C ++ 0x将有更好的解决方案。

作为一个练习,容器可以接受T或包含T的构造函数的参数的对象。这仍然依赖于RVO(返回值优化)。

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();
}

答案 4 :(得分:-1)

不要这样做。临时在创建它的表达式之后立即销毁(除非它立即绑定到引用,在这种情况下它是引用的范围)。生命不能延伸到班级。

这就是为什么我永远不会将成员存储为引用 - 只复制对象或指针。对我来说,指针明显表明生命会发挥作用。特别是在构造函数的情况下,构造函数params必须比类本身更长,这是不明显的。