从函数返回STL对象而不触发移动

时间:2015-05-19 19:51:02

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

假设有一个函数返回任何实现移动语义的本地对象,例如:任何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边界上分配/删除内存,移动将导致内存断言。

3 个答案:

答案 0 :(得分:2)

  

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

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

答案 1 :(得分:2)

简短的回答是,你不能让return f;不动。当您使用C ++返回时,elision是默认值,如果不是,则移动它,如果不是,则复制它。如果您使用非平凡的声明 - 甚至true?v:vstatic_cast<whatever const&>(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<int>的{​​{1}}:类似地,标准布局可以非常安全地跨越DLL边界。

答案 2 :(得分:1)

执行static_cast以进行引用 (例子使用可移动的C类,可以是向量)

施放到左值参考会禁用移动和NRVO

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

强制转换为右值参考仅禁用NRVO

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