在不复制临时对象的情况下延长其生命周期
Extending the lifetime of a temporary object without copying it
考虑以下代码:
#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
的复制和移动构造函数已明确删除,以确保它只存在一个实例。
据我所知,如果函数的 return 值延长生命周期的唯一情况,
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 class 的“构造函数”。因此,您有责任确保传递的对象寿命更长。因此,您上面的代码包含 未定义的行为。
您看到的可能是 class 中最有可能的行为,即不引用任何成员。如果您要访问对象成员(包括 v-table),您很可能会观察到违规访问。
话虽如此,您的情况下的正确代码是:
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 把临时对象变成永久对象就这么简单
Why is the lifetime of object x not extended past the function call even if a const reference has been bound to it?
从技术上讲,对象的生命周期延长到函数调用之后。然而,它并没有扩展到 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
与 return 值相关联。如果编译器没有 function
的定义,那么它 不能 知道传入的对象是被 returned 的引用。没有这些知识,它就无法知道延长 x
的生命周期。
现在,你可能会说,如果提供了function
的定义,那么编译器就可以知道了。好吧,有复杂的逻辑链可能会阻止编译器在编译时知道。你可以这样做:
const object *minimum(const object &lhs, const object &rhs)
{
return lhs < rhs ? lhs : rhs;
}
好吧,return 是对其中之一的引用,但是哪一个将仅根据对象的运行时值来确定。调用者应该延长谁的生命周期?
我们也不希望代码的行为根据编译器是否只有声明或完整定义而改变。要么 always 可以编译只有声明的代码,要么 never 只编译带有声明的代码(如inline
、constexpr
或 template
函数的情况)。声明可能会影响性能,但绝不会 行为 。这很好。
由于编译器可能没有识别参数 const&
超出函数生命周期所需的信息,即使它有该信息也可能不是可以静态确定的东西, C++ 标准不允许实现甚至 try 来解决问题。因此,每个 C++ 用户都必须认识到,如果它 return 是引用,则在临时对象上调用函数可能会导致问题。即使引用隐藏在其他某个对象中。
你想做的事做不到。这就是为什么你根本不应该让一个对象不可移动的原因之一,除非它对它的行为或性能是必不可少的。
考虑以下代码:
#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
的复制和移动构造函数已明确删除,以确保它只存在一个实例。
据我所知,如果函数的 return 值延长生命周期的唯一情况,
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 class 的“构造函数”。因此,您有责任确保传递的对象寿命更长。因此,您上面的代码包含 未定义的行为。
您看到的可能是 class 中最有可能的行为,即不引用任何成员。如果您要访问对象成员(包括 v-table),您很可能会观察到违规访问。
话虽如此,您的情况下的正确代码是:
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 把临时对象变成永久对象就这么简单
Why is the lifetime of object x not extended past the function call even if a const reference has been bound to it?
从技术上讲,对象的生命周期延长到函数调用之后。然而,它并没有扩展到 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
与 return 值相关联。如果编译器没有 function
的定义,那么它 不能 知道传入的对象是被 returned 的引用。没有这些知识,它就无法知道延长 x
的生命周期。
现在,你可能会说,如果提供了function
的定义,那么编译器就可以知道了。好吧,有复杂的逻辑链可能会阻止编译器在编译时知道。你可以这样做:
const object *minimum(const object &lhs, const object &rhs)
{
return lhs < rhs ? lhs : rhs;
}
好吧,return 是对其中之一的引用,但是哪一个将仅根据对象的运行时值来确定。调用者应该延长谁的生命周期?
我们也不希望代码的行为根据编译器是否只有声明或完整定义而改变。要么 always 可以编译只有声明的代码,要么 never 只编译带有声明的代码(如inline
、constexpr
或 template
函数的情况)。声明可能会影响性能,但绝不会 行为 。这很好。
由于编译器可能没有识别参数 const&
超出函数生命周期所需的信息,即使它有该信息也可能不是可以静态确定的东西, C++ 标准不允许实现甚至 try 来解决问题。因此,每个 C++ 用户都必须认识到,如果它 return 是引用,则在临时对象上调用函数可能会导致问题。即使引用隐藏在其他某个对象中。
你想做的事做不到。这就是为什么你根本不应该让一个对象不可移动的原因之一,除非它对它的行为或性能是必不可少的。