函数中的临时对象参数生存期

时间:2018-07-10 08:54:24

标签: c++

我已经阅读了几篇有关临时对象寿命的文章。总之,我了解到:

  

the temporary is destroyed after the end of the full-expression containing it.

但是此代码超出了我的期望:

#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。这是怎么了?

3 个答案:

答案 0 :(得分:24)

在C ++ 17之前的版本中,sp是从临时结构开始的移动结构,如果该移动没有开始的话。无论哪种情况,sp都是资源的唯一所有者,因此正确地将使用计数报告为1。这是this reference中的重载10)

虽然临时临时工仍然存在,但如果不消除,则处于移出状态,不再拥有任何资源,因此不会增加资源的使用量。

自C ++ 17起,由于保证了复制/移动省略,因此不会创建任何临时文件,并且就地构建了sp


所说reference的确切措辞:

  

10)从shared_ptr移动构造r。构造后,*this包含先前状态r的副本,r为空,其存储的指针为null。 [...]

在我们的情况下,r指的是临时变量,*this指的是sp

答案 1 :(得分:12)

有一个奇怪的概念,称为省略。

Elision是一个过程,通过该过程,编译器可以使用两个对象的生存期并将其合并。通常,人们说复制或移动构造函数“被消除了”,但真正消除的是两个看似不同的对象的 identity

根据经验,当使用匿名临时对象直接构造另一个对象时,它们的生存期可以被消除。所以:

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的一个实例;它只有很多名字。

中,情况更进一步。在此之前,所讨论的对象必须在逻辑上是可复制/移动的,并且省略省略就可以简单地调用构造函数并共享对象标识。

之后,某些曾经被省略的东西(在某种意义上)是“保证清除”,这实际上是另一回事。基本上,“保证省略”是指prvalues(在之前是临时的东西)现在是有关如何创建对象的抽象指令。

在某些情况下,可以从中实例化临时对象,但在其他情况下,它们只是用于在其他位置构造其他对象。

因此在中,您应该考虑以下功能:

A f();

作为返回关于如何创建A指令的函数。当您这样做时:

A a  = f();

您说的是“使用f返回的指令来构造名为A的{​​{1}}”。

类似地,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的论点。

在没有恶意编译器标志的[C ++ 17]之前的版本中,省略的结果在这里实际上是相同的。在这种情况下,临时身份将与std::shared_ptr<int>的参数合并。

在实际情况下,将不会有一个临时计数fun,其引用计数为0;其他声称这是错误的答案。发生这种情况的一种方法是,如果传入编译器无法执行省略的标志(上述恶意编译器标志)。

如果您确实传递了这样的标志,则shared_ptr将移入shared_ptr的参数中,并且它的引用计数为0。因此fun将保持为0。

答案 2 :(得分:3)

除了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进行显式编译,否则不会调用复制构造函数。在C ++ 17中,您甚至可以删除复制构造函数

A(const A&) = delete;

因为可以保证复制省略。考虑到这一点:您作为函数参数传递的临时对象“仅在包含临时对象的情况下才在包含它的完整表达式的结尾处被销毁”,并且代码段可能会建议即使它很容易(并且自C ++ 17:保证被优化)出来也存在。