15

我的代码如下所示:

class T {};

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

class adapter : T {};

container(adapter(), adapter());

我认为常量引用的生命周期将是容器的生命周期。但是,否则会在创建容器后销毁适配器对象,留下悬空引用。

什么是正确的寿命?

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

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

谢谢

4

5 回答 5

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 将支持右值引用(如果我没记错的话,是 2010 年 4 月 12 日)。我不知道 GCC 中右值引用的状态是什么。

于 2010-04-09T00:04:56.417 回答
6

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

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

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

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

于 2010-04-08T23:58:04.527 回答
1

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

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

模板<类型名 T>
类容器
{
    上市:
        容器(const T& first, const T& second) : first(first), second(second) {}
    私人的:
        首先是常量 T;
        常量 T 秒;
};

或者,您可以为对象命名以防止它们超出范围:

   适配器优先;
   第二个适配器;
   容器 c(第一,第二);

但是,我认为这不是一个好主意,因为诸如此类的声明return c是无效的。

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

模板<类型名 T>
类容器
{
    上市:
        容器(const boost::shared_ptr<const T>& first, const boost::shared_ptr<const T>& second) : first(first), second(second) {}
    私人的:
        boost::shared_ptr<const T> 首先;
        boost::shared_ptr<const T> 秒;
};

然后你可以使用:

boost::shared_ptr<const adapter> first(new adapter);
boost::shared_ptr<const adapter> second(new adapter);
容器<适配器> c(first,second);

或者,如果您想在本地拥有 first 和 second 的可变副本:

boost::shared_ptr<适配器> first(新适配器);
boost::shared_ptr<适配器> 第二(新适配器);
容器<适配器> c(boost::const_pointer_cast<const 适配器>(first),boost::const_pointer_cast<const 适配器>(second));
于 2010-04-08T23:58:45.840 回答
0

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

如果要调用默认构造函数,那应该没问题。只需调用 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();
}
于 2010-04-09T10:15:05.980 回答
-1

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

这就是为什么我从不将成员存储为引用 - 仅复制对象或指针。对我来说,指针很明显地表明了生命的重要性。尤其是在构造函数的情况下,你的构造函数参数必须比类本身更长寿,这一点并不明显。

于 2010-04-08T23:59:46.707 回答