【问题标题】:Return STL objects from function without triggering move从函数返回 STL 对象而不触发移动
【发布时间】:2015-07-31 19:52:27
【问题描述】:

假设有一个函数返回任何本地对象,它实现了移动语义,例如任何 STL 容器,例如 std::vectorstd::string 等。例如:

std::vector<int> return_vector(void)
{
    std::vector<int> tmp {1,2,3,4,5};
    return tmp;
}

默认情况下,tmp 将被视为右值,tmp 将被移动(或将进行返回值优化)。

问题是如何手动覆盖和避免C++11的默认行为并执行复制构造函数而不是移到这里?一种解决方案可能是为 std::vector 实现一个包装类,禁用移动语义,但这似乎不是一个好的选择。

这种愿望的一个原因可能是调用者在另一个程序集中,如果运行时库是静态链接的,那么会有两个堆。由于跨 DLL 边界分配/删除内存,移动将导致内存断言。

【问题讨论】:

  • 尝试通过跨 dll 边界的接口使用标准库类是危险的,期间,无论您是移动还是复制这样做。
  • 如果您在return_vector 中复制,那么该副本仍然会从最初分配tmp 的存储空间的同一堆中分配,因此您不会在那里保存任何内容。 (跨 DLL 的 C++ 要求痛苦)

标签: c++ c++11 stl move move-semantics


【解决方案1】:

问题是如何避免C++11的默认行为并执行复制构造函数而不是移到这里?

这不是默认行为。在这种情况下,默认行为是省略副本。此举只会在实施未实施 NRVO 的不太可能的情况下发生。

【讨论】:

    【解决方案2】:

    简短的回答是,你不能让return f; 不动。当你在 C++ 中返回时,省略是默认的,如果不是,它被移动,如果不是,它被复制。如果您使用重要的语句——甚至是true?v:vstatic_cast&lt;whatever const&amp;&gt;(v)——它将阻止自动移动并强制复制。但这对你没有帮助。

    避免移动对你没有帮助。返回对象仍然在函数内创建,并由调用代码处理。

    现在,并非一切都丢失了。您可以通过使用头文件(存在于客户端代码中)进行分配和 DLL 安全接口(到实现)来避免这种情况。

    这里我设计了一个sink类型,分批吸入T类型的数据。然后它用 pvoid 调用一些函数指针并完成。

    template<class T>
    struct sink {
      void* s;
      void(*)(void*, T const*, T const*) f;
      void operator()(T const& t)const{ f(s, &t, (&t)+1); }
      void operator()(std::initializer_list<T> il) {
        f(s, il.begin(), il.end());
      }
    };
    template<class T, class A>>
    sink<T> vector_sink( std::vector<T, A>& out ) {
      return {&out, +[](void* s, T const* b, T const* e){
        auto* pout = static_cast<std::vector<T,A>*>(s);
        pout->insert( pout->end(), b, e );
      }};
    }
    

    现在,从 DLL 导出:

    void make_data(sink<int> s) {
      s({1,2,3,4,5});
    }
    

    在头文件中,暴露:

    void make_data(sink<int> s);
    std::vector<int> return_vector() {
      std::vector<int> r;
      make_data( vector_sink(r) );
    }
    

    现在vector 完全存在于DLL 的客户端代码中。只有一个标准布局类(由 2 个指针组成)穿过 DLL 屏障。

    更高级的sink 可以通过添加一个新函数(用于移动数据)来区分右值和左值。但是,如果这是为了桥接 DLL 边界,这似乎是不明智的。

    这处理“返回”一个向量。要输入一个向量(不附加),我建议编写 array_view&lt;int&gt; 来包装两个 int*s:同样,标准布局可以非常安全地跨越 DLL 边界。

    【讨论】:

      【解决方案3】:

      执行一个 static_cast 来引用就可以了 (示例使用可移动类C,可以是向量)

      转换为左值引用禁用移动和 NRVO

      C f() {
          C c;
          return static_cast<C&>(c);
      }
      

      转换为右值引用仅禁用 NRVO

      C f() {
          C c;
          return static_cast<C&&>(c);
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-11-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-02-29
        • 2021-09-11
        • 2015-11-26
        相关资源
        最近更新 更多