ead*_*ead 67 c++ object-lifetime language-lawyer copy-elision c++17
这似乎是一个愚蠢的问题,但是return xxx;在一个明确定义的函数中"执行" 的确切时刻?
请参阅以下示例以了解我的意思(现在直播):
#include <iostream>
#include <string>
#include <utility>
//changes the value of the underlying buffer
//when destructed
class Writer{
public:
std::string &s;
Writer(std::string &s_):s(s_){}
~Writer(){
s+="B";
}
};
std::string make_string_ok(){
std::string res("A");
Writer w(res);
return res;
}
int main() {
std::cout<<make_string_ok()<<std::endl;
}
Run Code Online (Sandbox Code Playgroud)
我天真地期待发生的事情make_string_ok被称为:
res被调用(价值res就是"A")w调用构造函数return res被执行.应该返回res的当前值(通过复制当前值res),即"A".w被称为析构函数,值res变为"AB".res函数被称为.所以我希望"A"结果,但"AB"打印在控制台上.
另一方面,对于略有不同的版本make_string:
std::string make_string_fail(){
std::pair<std::string, int> res{"A",0};
Writer w(res.first);
return res.first;
}
Run Code Online (Sandbox Code Playgroud)
结果如预期 - "A"(见现场).
标准是否规定了应在上述示例中返回哪个值,还是未指定?
Shl*_*oim 36
由于返回值优化(RVO),可能无法调用std::string resin 的析构函数make_string_ok.该string对象可以在呼叫者的侧被构造和功能可以仅初始化的值.
代码相当于:
void make_string_ok(std::string& res){
Writer w(res);
}
int main() {
std::string res("A");
make_string_ok(res);
}
Run Code Online (Sandbox Code Playgroud)
这就是为什么价值回报应为"AB".
在第二个示例中,RVO不适用,并且该值将在调用return时完全复制到返回值,并且Writer析构函数将res.first在复制发生后运行.
6.6跳转声明
上从范围(但是完成)出口,析构函数(12.4)被称为用于与自动存储持续时间(3.7.2),其在该范围中声明,在它们的声明相反的顺序进行(命名对象或临时变量)的所有构造的对象.从循环中移出一个循环,或者从具有自动存储持续时间的初始化变量返回过去涉及销毁具有自动存储持续时间的变量,这些变量在从...转移的点处的范围内.
...
6.6.3退货声明
返回实体的副本初始化是在由return语句,这反过来,是当地的变量(6.6)的破坏之前测序的操作建立完整的表达年底临时工破坏之前测序封闭return语句的块.
...
12.8复制和移动类对象
31当满足某些条件时,允许实现省略类对象的复制/移动构造,即使对象的复制/移动构造函数和/或析构函数具有副作用.在这种情况下,实现将省略的复制/移动操作的源和目标视为仅仅两种不同的引用同一对象的方式,并且该对象的销毁发生在两个对象的后期时间.在没有优化的情况下销毁.(123)在下列情况下允许复制/移动操作的省略,称为复制省略(可以合并以消除多个副本):
- 在具有相同cvunqualified类型函数返回类型与类返回类型的函数返回语句,当表达是一种非挥发性的自动对象的名称(不是函数或catch子句参数其他),则通过将自动对象直接构造到函数的返回值中,可以省略复制/移动操作
123)因为只有一个对象被破坏而不是两个,并且没有执行一个复制/移动构造函数,所以仍然有一个对象被破坏.
luk*_*k32 28
它是RVO(+返回副本作为临时模糊图片),允许更改可见行为的优化之一:
10.9.5复制/移动省略 (重点是我的):
当满足某些条件时,允许实现省略类对象的复制/移动构造,即使为复制/移动操作选择的构造函数和/或对象的析构函数具有副作用**.在这种情况下,实现将省略的复制/移动操作的源和目标简单地视为引用同一对象的两种不同方式.
复制/移动操作的省略,称为复制省略,在以下情况下允许(可以合并以消除多个副本):
- 在具有类返回类型的函数的return语句中,当表达式是具有相同类型的非易失性自动对象的名称(除了函数参数或由处理程序的异常声明引入的变量)之外(忽略cv-qualification)作为函数返回类型,通过将自动对象直接构造到函数调用的返回对象中,可以省略复制/移动操作
- [...]
根据它是否适用你的整个前提出错.在1. res被称为c'tor ,但对象可能住在里面make_string_ok或外面.
子弹2和3可能根本不会发生,但这是一个侧面点.目标受到影响的副作用Writer,在外面make_string_ok.这恰好是make_string_ok在评估环境中使用的临时创建operator<<(ostream, std::string).编译器创建了一个临时值,然后执行该函数.这很重要,因为临时生活在它之外,所以目标Writer不是本地的,make_string_ok而是为了operator<<.
同时,您的第二个示例不符合标准(也不是为了简洁省略的标准),因为类型不同.所以作家去世了.它甚至会死,如果它是它的一部分pair.所以在这里,一个副本res.first作为临时对象返回,然后dtor Writer影响原始res.first,即将死亡.
很明显,副本是在调用析构函数之前制作的,因为副本返回的对象也会被销毁,所以你无法复制它.
毕竟它归结为RVO,因为Writer无论是否应用优化,都可以在外部对象或本地对象上工作.
不,优化是可选的,但它可以改变可观察的行为.编译器可自行决定是否应用它.它可以免除"一般的as-if"规则,该规则允许编译器进行任何不会改变可观察行为的转换.
它的一个案例在c ++ 17中成为强制性的,但不是你的.强制性的是返回值是未命名的临时值.
Yak*_*ont 16
C++中有一个名为elision的概念.
Elision采用两个看似不同的对象,并将其身份和生命融合在一起.
在c ++ 17之前,可能会出现省略:
如果Foo f;在返回的函数中有非参数变量Foo,则return语句很简单return f;.
当您使用匿名对象构建几乎任何其他对象时.
在c ++ 17中,所有(几乎?)#2的情况都被新的prvalue规则消除了; elision不再出现,因为用于创建临时对象的东西不再这样做.相反,"临时"的构造直接绑定到永久对象位置.
现在,鉴于编译器编译的ABI,并不总是可以省略.可能出现的两种常见情况称为返回值优化和命名返回值优化.
RVO就是这样的情况:
Foo func() {
return Foo(7);
}
Foo foo = func();
Run Code Online (Sandbox Code Playgroud)
我们有一个返回值Foo(7),它被省略到返回的值,然后被省略到外部变量中foo.似乎是3个对象(返回值foo(),return行上的值,和Foo foo)在运行时实际上是1.
在c ++ 17之前,复制/移动构造函数必须存在于此处,并且省略是可选的; 在c ++ 17中,由于新的prvalue规则不需要复制/移动构造,并且编译器没有选项,这里必须有1个值.
另一个着名的案例是命名返回值优化,NRVO.这是上面的(1)省略案例.
Foo func() {
Foo local;
return local;
}
Foo foo = func();
Run Code Online (Sandbox Code Playgroud)
再次,省音可以合并的寿命和身份Foo local,从返回值func和Foo foo外部func.
即使是c ++ 17,第二个合并(在func返回值和之间Foo foo)也是非可选的(从技术上讲,返回的prvalue func永远不是一个对象,只是一个表达式,然后绑定到构造Foo foo),但第一个仍然是可选的,并要求存在移动或复制构造函数.
即使消除这些副本,破坏和构造会产生可观察到的副作用,Elision也是可以发生的规则; 它不是"假设"优化.相反,它是一个微妙的变化,远离一个天真的人可能认为C++代码意味着什么.将其称为"优化"不仅仅是一种误称.
它是可选的,而微妙的东西可以打破它,这是一个问题.
Foo func(bool b) {
Foo long_lived;
long_lived.futz();
if (b)
{
Foo short_lived;
return short_lived;
}
return long_lived;
}
Run Code Online (Sandbox Code Playgroud)
在上述情况下,虽然它是合法的编译器都的Elid Foo long_lived和Foo short_lived,执行问题使其基本不可能,因为这两个对象不能都有自己的寿命合并的返回值func; 躲避short_lived和long_lived团结是不合法的,他们的生命重叠.
您仍然可以在as-if下执行此操作,但前提是您可以检查并了解析构函数,构造函数和构造函数的所有副作用.futz().