代码之家  ›  专栏  ›  技术社区  ›  Rick Jim DeLaHunt

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

c++
  •  25
  • Rick Jim DeLaHunt  · 技术社区  · 7 年前

    我读过几篇关于临时对象的文章。总之,我知道:

    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));  
    
    }
    

    所以我想我这里有两个智能指针对象,一个是 std::make_shared<int>(5) ,临时未命名对象和另一个 sp 它是函数内部的局部变量。所以根据我的理解,临时的不会在完成函数调用之前“死亡”。我希望输出是2而不是1。这里怎么了?

    3 回复  |  直到 7 年前
        1
  •  24
  •   Baum mit Augen    7 年前

    Pre-C++17, sp 是从临时移动构造的,如果移动没有被省略。无论哪种情况, 服务提供商 是资源的唯一所有者,因此使用计数正确地报告为1。这是过载10) 阿西 在里面 this reference .

    虽然临时文件仍然存在,但如果不删除,它将处于“已移出”状态,并且不再拥有任何资源,因此它不会影响资源的使用计数。

    由于C++ 17,所以没有复制的临时性,因为有保证的拷贝/移动删除,以及 服务提供商 施工到位。


    阿西 来自said的确切措辞 reference :

    10)移动构件 shared_ptr r . 施工结束后, *this 包含以前状态的副本 R , R 为空,其存储指针为空。[…]

    在我们的例子中, R 指临时和 *此 服务提供商 .

        2
  •  12
  •   Yakk - Adam Nevraumont    7 年前

    有一个奇怪的概念叫做省略。

    省略是一个允许编译器获取两个对象的生存期并合并它们的过程。通常人们说复制或移动构造函数“被省略”,但真正被省略的是 身份 两个看似不同的物体。

    根据经验,当一个匿名的临时对象被用来直接构建另一个对象时,它们的生命周期可以一起省略。所以:

    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(过去在pre中是临时的东西- )现在是关于如何创建对象的抽象指令。

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

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

    A f();
    

    作为返回的函数 关于如何创建 . 当你这样做的时候:

    A a  = f();
    

    你在说“使用说明 f 返回以构造 命名 a “。

    同样地, A{} 不再是临时的,但说明不再是如何创建 . 如果你把它放在一行上,这些指令被用来创建一个临时的,但是在大多数情况下,逻辑上或实际上不存在临时的。

    template<class T, class...Us>
    std::shared_ptr<T> make_shared(Us&&...);
    

    这是一个函数,返回有关如何创建 shared_ptr<T> .

     fun(std::make_shared<int>(5)); 
    

    在这里,您将这些说明应用于 fun ,类型为 std::shared_ptr<int> .

    在没有敌方编译器标志的前-[c++ 17 ]中,省略的结果实际上是相同的。在这种情况下,临时身份与 乐趣 .

    在任何实际情况下都不会有临时的 shared_ptr 引用计数为0;声称这是错误的其他答案。一种可能发生的情况是,如果您传入编译器执行省略的标志(上面的恶意编译器标志)。

    如果你确实传递了这样的旗帜, 共享资源 从移到的参数中 乐趣 ,并且它的引用计数为0。所以 use_count 将保持0。

        3
  •  3
  •   lubgr    7 年前

    除搬家施工外 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:保证)被优化出来。