C ++中的函数如何以最小的复制返回值或引用?

时间:2014-06-21 18:15:24

标签: c++ c++11

我有一个委托给另外两个的函数,根据某些运行时条件返回引用或值:

X by_value() { ... }
const X& by_reference() { ... }

?? foo(bool b) {
  if (b) {
    return by_value();
  } else {
    return by_reference();
  }
}

我想选择我的函数的返回类型,以便调用者最小化复制; e.g:

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是未定义的行为。

是否可以通过返回某种代理来确保上述用途具有最少的副本?


解释:由于表格的显着推迟,你做错了#34;我想我会解释原因对此。

想象一下,我有一个T or function<T()>类型的数组(意味着此数组的元素属于T类型,或者它们的函数返回T)。通过&#34;值&#34;这个数组的元素,我的意思是,值本身或评估函数时的返回值。

如果这个get_value_of_array(int index)按值返回,那么在数组只包含一个元素的情况下,我被迫做一个额外的副本。这就是我试图避免的。


进一步说明:如果答案是,&#34;那是不可能的&#34;,那对我很好。我很乐意看到这方面的证据 - 理想情况下的形式&#34;假设有Proxy<X>类型解决了你的问题。然后...`

6 个答案:

答案 0 :(得分:6)

您要找的是sum-type(即,可能的值为“可能的X加上可能的X const&值的类型“)。

在C ++中,这些通常称为variant。这些通常实现为标记加上适当大小和对齐的数组,并且在运行时仅保留一个值。或者,它们通过动态分配和经典visitor pattern实现。

例如,使用Boost.Variant,您可以声明函数返回boost::variant<X, X const&>live example):

boost::variant<X, X const&> foo(bool b) {
  if (b) {
    return by_value();
  } else {
    return by_reference();
  }
}

答案 1 :(得分:1)

我认为这是不可能的,因为调用者是否决定移动或复制返回值(无论是来自代理还是来自您自己的类)是编译时决定,而您想要的是制作这是一个运行时的决定。在运行时不能发生过载分辨率。

我能看到的唯一出路就是让被调用者决定这一点,即提供一个T &参数,它可以移动 - 分配或复制 - 分配给依赖在它认为合适的地方 或者,您可以传递一个aligned_storage<sizeof(T)>缓冲区并让被调用者在其中构造值,如果您不认为调用者可以生成一个&#34; null&#34;某种实例。

答案 2 :(得分:1)

好吧,如果真的希望实现这一点,那么这是一个相当丑陋的方式:

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,这样会更简单。

答案 3 :(得分:0)

你的目标很糟糕:有多个返回类型,其中一个是副本,另一个是引用,这使得函数无法预测。

假设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() 

悬空参考

答案 4 :(得分:0)

您可以通过将变量类型传递给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 and hth. - Alf实施X。

答案 5 :(得分:-2)

在我写这个答案的时候,并不要求函数foo的参数应该在运行时计算,或者应该被允许不是字面false或{ {1}},因此:

true