假设有一个函数返回任何实现移动语义的本地对象,例如:任何STL容器,例如std::vector
,std::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边界上分配/删除内存,移动将导致内存断言。
答案 0 :(得分:2)
问题是如何避免C ++ 11的默认行为并执行复制构造函数而不是移动到这里?
这不是默认行为。在这种情况下,默认行为是忽略副本。此举只会在实施不实施NRVO的不太可能的情况下发生。
答案 1 :(得分:2)
简短的回答是,你不能让return f;
不动。当您使用C ++返回时,elision是默认值,如果不是,则移动它,如果不是,则复制它。如果您使用非平凡的声明 - 甚至true?v:v
或static_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);
}