繁体   English   中英

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

[英]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 ++ 17,如果移动没有被省略,则sp是从临时移动构造的。 在任何一种情况下, sp都是资源的唯一所有者,因此使用计数正确地报告为1.这是过载10) 此参考中

虽然临时仍然存在,但如果没有被删除,它处于移动状态并且不再保留任何资源,因此它不会对资源的使用计数产生影响。

从C ++ 17开始,由于保证了复制/移动省略,因此不会创建临时,并且sp就构建到位。


上述参考文献中的确切措辞:

10)从r构造一个shared_ptr 在构造之后, *this包含r的先前状态的副本, r为空并且其存储的指针为空。 [...]

在我们的例子中, r指的是临时的, *this指的是sp

有一个奇怪的概念,称为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实例; 它只有很多名字。

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

一些以前被省略的东西(在某种意义上)是“保证省略”,这实际上是另一回事。 “保证省音”基本想法prvalues(东西用来在预临时对象 )现在是关于如何创建对象抽象的指令。

在某些情况下,临时实例是从它们中实例化的,但在其他情况下,它们仅用于在其他某些地方构建其他对象。

所以在你应该想到这个函数:

A f();

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

A a  = f();

你说“使用f返回的指令来构造一个名为aA ”。

同样, 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM