C++ C++;恒定参考寿命(容器适配器)

C++ C++;恒定参考寿命(容器适配器),c++,reference,constants,lifetime,C++,Reference,Constants,Lifetime,我有如下代码: class T {}; class container { const T &first, T &second; container(const T&first, const T & second); }; class adapter : T {}; container(adapter(), adapter()); 我认为常量引用的生命周期就是容器的生命周期。 然而,看起来并非如此,适配器对象在创建容器后被销毁,留下悬空引用 什么是正确

我有如下代码:

class T {};

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

class adapter : T {};

container(adapter(), adapter());
我认为常量引用的生命周期就是容器的生命周期。 然而,看起来并非如此,适配器对象在创建容器后被销毁,留下悬空引用

什么是正确的寿命

适配器临时对象的堆栈范围是容器对象的范围还是容器构造函数的范围

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


感谢

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


没有办法更改/覆盖/延长临时人员的此生命周期。如果想要更长的生命周期,请使用实际对象而不是临时对象:

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

或者更好的方法是,不要对类成员使用常量引用,除非在最糟糕的情况下,对象关系非常密切,而且绝对不是临时的。

引用将在容器的整个生命周期内存在,但被引用的对象只会在该对象的生命周期内存在。在这种情况下,您已经将您的引用绑定到具有自动存储分配(“栈分配”)的临时对象(如果您愿意,尽管这不是C++术语)。因此,您不能期望临时语句存在于编写它的语句之外(因为它在调用
容器的构造函数后立即超出范围)。处理这一问题的最佳方法是使用副本,而不是引用。因为您使用的是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; }; 然后,您可以使用:

boost::shared_ptr<const adaptor> first(new adaptor); boost::shared_ptr<const adaptor> second(new adaptor); container<adaptor> c(first,second); boost::先共享ptr(新适配器); boost::共享的ptr秒(新适配器); 容器c(第一、第二); 或者,如果您希望在本地拥有第一个和第二个的可变副本:

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)); boost::先共享ptr(新适配器); boost::共享的ptr秒(新适配器); 容器c(boost::const_pointer_cast(第一个),boost::const_pointer_cast(第二个));
不要这样做。临时变量在创建它的表达式之后立即被销毁(除非它立即绑定到引用,在这种情况下它是引用的范围)。不能将生存期扩展到类的生存期


这就是为什么我从不将成员存储为引用—只存储复制的对象或指针。对我来说,指针让我明白,人生就是为了比赛。特别是在构造函数的情况下,构造函数参数必须超过类本身的寿命是不明显的。

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

引用绑定到的临时对象或作为临时对象绑定到的子对象的完整对象的临时对象将在引用的生存期内保持,以下指定的情况除外。构造函数的ctor初始值设定项(12.6.2)中与引用成员的临时绑定将持续存在,直到构造函数退出临时绑定到函数调用(5.2.2)中的引用参数,直到包含该调用的完整表达式完成为止。

因此,虽然绑定临时对象是延长临时对象()生命周期的一种高级技术,但在这种情况下显然没有帮助

另一方面,Eric Niebler有一篇您可能感兴趣的文章,该文章讨论了一种有趣的(如果复杂的话)技术,它可以让类的构造函数推断是临时对象(实际上是右值)被传递给它(因此必须被复制)还是非临时对象(左值)(因此有可能安全地将参考资料隐藏起来,而不是复制):

但祝你好运——每次我读到这篇文章,我都要把所有的东西都看一遍,就好像我从来没有看过这篇文章一样。它只会让我停留在短暂的瞬间


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

如果要避免复制,那么我认为容器必须自己创建存储的实例

如果您想调用默认构造函数,那么应该没有问题。只需调用容器的默认构造函数即可

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

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

模板
类构造_与_1
{
T1_1;
公众:
用_1(常数T1&T1)构造_:1(T1){
模板
U construct()常量{返回U(_1);}
};
模板
类构造_与_2
{
T1_1;
T2 2;
公众:
用_2构造_(常数T1和T1,常数T2和T2):_1(T1),_2(T2){
模板
U construct()常量{返回U(_1,_2);}
};
//其他算术等
模板
构造带1的构造带(常数T1和T1)
{
返回构造_和_1(t1);
}
模板
用2构造(常数T1和T1,常数T2和T2)
{
返回构造_和_2(t1,t2);
}
//等
模板
T构造(const T&source){return source;}
模板
T构造(cons)
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));
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();
}