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

发布于 2024-08-28 08:52:46 字数 381 浏览 8 评论 0原文

我的代码如下所示:

class T {};

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

class adapter : T {};

container(adapter(), adapter());

我认为常量引用的生命周期就是容器的生命周期。 然而,看起来不然,适配器对象在创建容器后被销毁,留下悬空引用。

什么是正确的寿命?

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

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

谢谢

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

云之铃。 2024-09-04 08:52:46

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

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

因此,绑定临时值是一种延长参数生命周期的高级技术 。临时对象(GotW #88 :“最重要的常量”的候选者),在这种情况下它显然不会对您有帮助。

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

不过祝你好运 - 每次我读这篇文章时,我都必须完成所有内容,就好像我以前从未见过这些材料一样。它只在我的脑海中停留了短暂的一瞬间……

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

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.

二货你真萌 2024-09-04 08:52:46

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

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

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

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

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.

浅唱ヾ落雨殇 2024-09-04 08:52:46

该引用将在容器的整个生命周期中存在,但被引用的对象将仅在该对象的生命周期中存在。在这种情况下,您已将引用绑定到具有自动存储分配的临时对象(如果您愿意,可以称为“堆栈分配”,尽管这不是 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;
};

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

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

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));
温暖的光 2024-09-04 08:52:46

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

如果你想调用默认构造函数,那么应该没有问题。只需调用 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();
}

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();
}
怎言笑 2024-09-04 08:52:46

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

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文