在不复制临时对象的情况下延长其生存期

Extending the lifetime of a temporary object without copying it

本文关键字:生存期 情况下 复制 临时对象      更新时间:2023-10-16

请考虑以下代码:

#include <utility>
#include <iostream>
struct object {
object(const object&) = delete;
object(object&&) = delete;
object() {std::clog << "object::object()n";}
~object() {std::clog << "object::~object()n";}
void operator()() const {std::clog << "object::operator()()n";}
};
struct wrapper {
const object& reference;
void operator()() const {reference();}
};
template <class Arg>
wrapper function(Arg&& arg) {
wrapper wrap{std::forward<Arg>(arg)};
return wrap;
}
int main(int argc, char* argv[]) {
wrapper wrap = function(object{}); // Let's call that temporary object x
wrap();
return 0;
}

我真的很惊讶它打印

object::object()
object::~object()
object::operator()()

问题 1:为什么对象x的生存期不会超过函数调用,即使 const 引用已绑定到它?

问题 2:有没有办法实现wrapper,以便它将x的生存期延长到函数调用之后?

注意:object的复制和移动构造函数已被显式删除,以确保它只存在一个实例。

为什么对象 x 的生存期不会超过函数调用,即使 const 引用已绑定到它?

从技术上讲,对象的生存期延长到函数调用之后。然而,它并没有扩展到wrap初始化之后。但这是一个技术问题。

在我们深入研究之前,我将进行简化:让我们摆脱wrapper。另外,我正在删除模板部分,因为它也无关紧要:

const object &function(const object &arg)
{
return arg;
}

这完全不会改变代码的有效性。

鉴于此声明:

const object &obj = function(object{}); // Let's call that temporary object x

你想要的是编译器识别"对象x"和obj引用同一个对象,因此应该延长临时对象的生存期。

那是不可能的。编译器不能保证有足够的信息来了解这一点。为什么?因为编译器可能只知道这一点:

const object &function(const object &arg);

看,function的定义将arg返回值相关联。如果编译器没有function的定义,那么它就无法知道传入的对象是返回的引用。没有这些知识,它就无法延长x的寿命。

现在,你可能会说,如果提供了function的定义,那么编译器就可以知道。嗯,有复杂的逻辑链可能会阻止编译器在编译时知道。您可以这样做:

const object *minimum(const object &lhs, const object &rhs)
{
return lhs < rhs ? lhs : rhs;
}

好吧,这将返回对其中一个的引用,但哪个将仅根据对象的运行时值来确定。呼叫者应延长谁的寿命?

我们也不希望代码的行为根据编译器是只有声明还是具有完整定义而更改。如果代码只有一个声明,则编译代码总是可以的,或者只使用声明编译代码永远不行(如inlineconstexprtemplate函数的情况(。声明可能会影响性能,但不会影响行为。这很好。

由于编译器可能没有识别参数const&超过函数生存期所需的信息,即使它具有该信息,也可能不是可以静态确定的东西,因此C++标准甚至不允许实现尝试解决问题。因此,每个C++用户都必须认识到,如果临时函数返回引用,则调用函数可能会导致问题。即使引用隐藏在某个其他对象中。

你想要的做不到。这就是为什么您不应该使对象完全不可移动的原因之一,除非它对其行为或性能至关重要。

据我所知,如果对于函数的返回值,则生存期延长的唯一情况,

struct A { int a; };
A f() { A a { 42 }; return a`}
{
const A &r = f(); // take a reference to a object returned by value
...
// life or r extended to the end of this scope
}

在代码中,将引用传递给 A 类的"构造函数"。因此,您有责任确保传递的对象存活更长时间。因此,上面的代码包含未定义的行为

您看到的可能是不引用任何成员的类中最可能的行为。如果要访问对象成员(包括向量表(,则很可能会观察到冲突访问。

话虽如此,在您的情况下,正确的代码是:

int main(int argc, char* argv[]) 
{
object obj {};
wrapper wrap = function(obj);
wrap();
return 0;
}

也许你想要的是将临时对象移动到包装器中:

struct wrapper {
wrapper(object &&o) : obj(std::move(o)) {}
object obj;
void operator()() const {obj();}
};

无论如何,原始代码没有多大意义,因为它是围绕错误的假设构建的,并且包含未定义的行为

临时对象的生存期实质上是创建它的表达式的结束。也就是说,当处理wrap = function(object{})完成时。

所以在简历中:

答案 1因为您尝试将生命周期延长应用于标准中指定的上下文以外的上下文。

答案 2就像将临时对象移动到永久对象一样简单。