[英]Lifetime of temporary object: iterator to temporary vector in nested function call
[英]Temporary object argument lifetime in a function
我已经阅读了几篇关于临时对象生命周期的帖子。 总之,我了解到:
但是这段代码超出了我的预期:
#include <memory>
#include <iostream>
void fun(std::shared_ptr<int> sp)
{
std::cout << "fun: sp.use_count() == " << sp.use_count() << '\n';
//I expect to get 2 not 1
}
int main()
{
fun(std::make_shared<int>(5));
}
所以我认为这里有2个智能指针对象,一个是std::make_shared<int>(5)
,临时的未命名对象,另一个sp
是函数内部的局部变量。 所以根据我的理解,临时的不会在完成函数调用之前“死”。 我希望输出为2而不是1.这里有什么问题?
c ++有一个奇怪的概念,称为elision。
Elision是一个允许编译器获取两个对象的生命周期并合并它们的过程。 通常人们会说复制或移动构造函数“被省略”,但真正省略的是两个看似不同的对象的身份 。
根据经验,当使用匿名临时对象直接构造另一个对象时,它们的生命周期可以一起消除。 所以:
A a = A{}; // A{} is elided with a
void f(A);
f(A{}); // temporary A{} is elided with argument of f
A g();
f(g()); // return value of g is elided with argument of f
在某些情况下,可以使用返回值来省略命名变量,并且可以将两个以上的对象一起省略:
A g() {
A a;
return a; // a is elided with return value of g
}
A f() {
A x = g(); // x is elided with return value of g
// which is elided with a within g
return x; // and is then elided with return value of f
}
A bob = f(); // and then elided with bob.
上述代码中只存在一个A
实例; 它只有很多名字。
在c ++ 17中,情况更进一步。 在此之前,所讨论的对象必须在逻辑上是可复制的/可移动的,并且简单地消除了elision调用构造函数并共享对象标识。
在c ++ 17之后,一些以前被省略的东西(在某种意义上)是“保证省略”,这实际上是另一回事。 “保证省音”基本想法prvalues(东西用来在预临时对象C ++ 17 )现在是关于如何创建对象抽象的指令。
在某些情况下,临时实例是从它们中实例化的,但在其他情况下,它们仅用于在其他某些地方构建其他对象。
所以在c ++ 17中你应该想到这个函数:
A f();
作为返回如何创建A
指令的函数。 当你这样做:
A a = f();
你说“使用f
返回的指令来构造一个名为a
的A
”。
同样, A{}
不再是临时的,而是指示如何创建A
如果你把它单独放在一行,那么这些指令用于创建一个临时的,但在大多数情况下,逻辑或实际上不存在暂时的。
template<class T, class...Us>
std::shared_ptr<T> make_shared(Us&&...);
这是一个返回有关如何创建shared_ptr<T>
指令的函数。
fun(std::make_shared<int>(5));
在这里,您将这些说明应用于fun
的agument,其类型为std::shared_ptr<int>
。
在没有恶意编译器标志的前[C ++ 17]中,elision的结果在这里实际上是相同的。 在这种情况下,临时身份与fun
的论点合并。
在任何实际情况下,都会有一个引用计数为0的临时shared_ptr
; 声称这是错误的其他答案。 可能发生这种情况的一种方法是,如果你传入编译器执行elision的标志(上面的恶意编译器标志)。
如果确实传入了这样的标志,那么shared_ptr
被移动到fun
的参数中,并且它的引用计数为0.因此use_count
将保持为0。
除了std::shared_ptr
的移动构造之外,还有另一个需要考虑的方面:按值传递的函数参数的就地创建。 这是编译器通常所做的优化。 考虑示例性类型
struct A {
A() { std::cout << "ctor\n"; }
A(const A&) { std::cout << "copy ctor\n"; }
};
与一个按值获取A
实例的函数一起使用
void f(A) {}
当函数参数作为这样的右值传递时
f(A{});
除非使用-fno-elide-constructors
显式编译,否则不会调用复制构造-fno-elide-constructors
。 在C ++ 17中,您甚至可以删除复制构造函数
A(const A&) = delete;
因为复制省略是有保证的。 考虑到这一点:作为函数参数传递的临时对象是“在包含它的完整表达式结束后销毁”, 只有在存在临时对象时 ,并且代码片段可能表明存在一个,即使它很容易(并且因为C ++ 17:保证会被优化)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.