c++中的函数如何以最小的复制返回值或引用?
How can a function in C++ return either a value or a reference with minimal copying?
我有一个函数委托给另外两个函数,根据运行时条件返回引用或值:
X by_value() { ... }
const X& by_reference() { ... }
?? foo(bool b) {
if (b) {
return by_value();
} else {
return by_reference();
}
}
我想选择函数的返回类型,以便调用者诱导最小的复制;例如:
const X& x1 = foo(true); // No copies
const X& x2 = foo(false); // No copies
X x3 = foo(true); // No copies, one move (or zero via RVO)
X x4 = foo(false); // One copy
在除最后一种情况外的所有情况下,都不需要(基于运行时行为)复制返回值。
如果foo
的返回类型为X
,则在情形2中将有一个额外的副本;但如果返回类型为const X&
,则情况1和3为未定义行为。
是否有可能通过返回某种代理来确保上述使用具有最小的副本?
解释:由于"you're doing it wrong"的形式有很大的阻力,我想我应该解释一下原因。
假设我有一个类型为T or function<T()>
的数组(意味着该数组的元素要么是类型为T
的,要么是返回T
的函数)。我所说的数组元素的"值"是指值本身或函数求值时的返回值。
如果这个get_value_of_array(int index)
按值返回,那么在数组只包含一个元素的情况下,我被迫进行额外的复制。这是我想避免的。
进一步说明:如果答案是"那是不可能的",那对我来说没关系。我很想看到这一点的证明——理想的形式是"假设有一个类型Proxy<X>
解决了你的问题。然后……"
您要查找的是求和类型(也就是说,其可能值为"可能的X
值加上可能的X const&
值"的类型)。
在c++中,通常称为variant
。它们通常被实现为一个标签加上一个适当大小和对齐的数组,并且在运行时只保留一个值。或者,它们可以通过动态分配和经典访问者模式实现。
例如,使用Boost。变体,您可以声明您的函数返回boost::variant<X, X const&>
(实际示例):
boost::variant<X, X const&> foo(bool b) {
if (b) {
return by_value();
} else {
return by_reference();
}
}
我认为这是不可能的,因为调用者是否决定移动或复制返回值(无论是来自代理还是来自您的类本身)是编译时决策,而您想要的是使其成为运行时决策。重载解析不能在运行时发生。
我能看到的唯一出路是让被调用者决定这一点,即通过提供一个T &
参数,它可以根据它认为合适的方式移动分配或复制分配。
或者,您可以传递一个aligned_storage<sizeof(T)>
缓冲区,并让被调用者在其中构造值,如果您不认为调用者可以期望创建某种类型的"null"实例。
好吧,如果真的想要实现这一点,这里有一个相当难看的方法:
X *foo(bool b, X *value) {
if (b) {
*value = get_value();
} else {
value = get_pointer_to_value();
}
return value;
}
使用例子:
void examplefunc() {
X local_store;
X *result;
result = foo(true, &local_store);
assert(result == &local_store);
use_x_value(*x);
result = foo(false, &local_store);
assert(result != &local_store);
use_x_value(*x);
}
上面的方法很麻烦:它需要两个局部变量,并且强制通过指针使用返回值。它还暴露了一个原始指针,它不能很好地转换为智能指针(将local_store
放入堆中以允许使用智能指针会使这种方法更加复杂,更不用说增加堆分配的开销)。另外,local_store
总是默认构造的,但是如果你不需要让examplefunc
可重入,它可以被设置为static
(或者在多线程版本中使用线程本地存储)。
所以我很难想象你会在什么地方使用这个。总是只返回一个复制的值(让编译器在可能的情况下处理复制省略),或者总是返回一个引用,或者总是返回一个shared_ptr
,这样会更简单。
您的目标是错误的:拥有多个返回类型,其中一个是副本,另一个是引用,这会使函数不可预测。
假设foo是某类a, B的成员函数:
是
X A::foo() { return X(); }
X foo a = A().foo()
定义良好的
和
const X& B::foo() { return some_internal_x; }
const X& b = B().foo()
悬空引用
您可以通过将变量类型传递给get_value_of_array
来完成您想要的(仍然有点模糊)。这将允许它返回两种不同的类型,并根据数组成员是函数还是数组进行调整。
struct X
{
X() { std::cout << "Construct" << std::endl; }
X(X const&) { std::cout << "Copy" << std::endl; }
X(X&&) { std::cout << "Move" << std::endl; }
};
const X array;
X function() { return X(); }
template<typename ReturnType>
ReturnType get_value_of_array(bool);
template<>
const X& get_value_of_array<const X&>(bool /*isarray*/)
{
// if (isarray == false) return the cached result of function()
return array; // gotta build the example yo!
}
template<>
X get_value_of_array<X>(bool isarray)
{
return isarray ? array : std::move(function());
}
int main()
{
// Optimizations may vary.
const X& x1 = get_value_of_array<decltype(x1)>(true); // No copies or moves
const X& x2 = get_value_of_array<decltype(x2)>(false); // No copies or moves.
X x3 = get_value_of_array<decltype(x3)>(true); // One copy, one move.
X x4 = get_value_of_array<decltype(x4)>(false); // Two moves.
}
感谢Cheers和hth。
在我写这个答案的时候,它不是一个要求,函数foo
的参数应该在运行时计算,或者应该被允许是字面上false
或true
以外的,因此:
#include <utility>
#include <iostream>
namespace my {
using std::cout; using std::endl;
class X
{
private:
X& operator=( X const& ) = delete;
public:
X() {}
X( X const& )
{ cout << "Copy" << endl; }
X( X&& )
{ cout << "Move" << endl; }
};
} // my
auto foo_true()
-> my::X
{ return my::X(); }
auto foo_false()
-> my::X const&
{ static my::X const static_x; return static_x; }
#define foo( arg ) foo_##arg()
auto main() -> int
{
using namespace my;
cout << "A" << endl; const X& x1 = foo(true); // No copies
cout << "B" << endl; const X& x2 = foo(false); // No copies
cout << "C" << endl; X x3 = foo(true); // No copies, one move (or zero via RVO)
cout << "D" << endl; X x4 = foo(false); // One copy
}
- 如何将不可移动和不可复制的函数返回值获取到数组中
- 将参数的省略复制为返回值
- 复制省略并在返回值中移动语义
- 返回值的复制构造函数何时发生
- 为什么在传递重载运算符的返回值时不调用复制构造函数
- 返回值优化并复制C中的ELINION
- 在从函数返回值时使用std::move()以避免复制
- 返回值优化是否需要声明一个复制构造函数
- 复制构造函数错误:从构造函数返回值
- 从具有移动语义或返回值优化的函数返回值,但不返回复制构造函数
- 返回无法按值复制的对象
- 是指向在向函数返回值/参数和类请求传输数据期间复制的内存的指针
- 按函数返回的值复制x值
- 按值从函数返回对象并在赋值/复制 C'tor C++ 中使用它
- 按值返回时复制构造函数的奇怪行为
- 返回值的确切复制时间
- 有没有办法使用 move 而不是复制语义将函数返回值(对象)包装在 Python 中
- C++对象作为返回值:复制或引用
- 如何将gcrypt库中gcry_cipher_encrypt的返回值复制到一个变量
- 返回值复制问题(以改进调试时间)-- 这里的解决方案是什么?