0

当返回一个容器时,我总是必须确定我应该使用返回值还是使用输出参数。如果性能很重要,我选择了第二个选项,否则我总是选择第一个选项,因为它更直观。

坦率地说,我个人一直强烈反对输出参数,可能是因为我的数学背景,但是当我没有其他选择时使用它们是可以的。

然而,当涉及到泛型编程时,情况已经完全改变了。在某些情况下,函数可能不知道它返回的对象是一个巨大的容器还是一个简单的值。

始终使用输出参数可能是我想知道是否可以避免的解决方案。如果我不得不这样做,那就太尴尬了

int a;
f(a, other_arguments);

相比

auto a = f(other_arguments);

此外,有时 的返回类型f()没有默认构造函数。如果使用输出参数,则没有优雅的方式来处理这种情况。

我想知道是否有可能返回一个“修饰符对象”,一个接受输出参数的仿函数来适当地修改它们。(也许这是一种惰性求值?)嗯,返回这样的对象不是问题,但问题是我不能插入一个适当的重载赋值运算符(或构造函数),它接受这样一个对象并触发它当返回类型属于我无法触及的库时,请完成它的工作,例如std::vector. 当然,转换运算符没有帮助,因为它们无法访问为目标对象准备的现有资源。

有人可能会问为什么不使用assign(); 定义一个具有begin()&的“生成器对象” end(),并将这些迭代器传递给std::vector::assign. 这不是一个解决方案。第一个原因,“生成器对象”没有对目标对象的完全访问权限,这可能会限制可以做的事情。对于第二个也是更重要的原因,我的函数的调用点f()也可能是一个模板,它不知道确切的返回类型f(),因此它无法确定assign()应该使用赋值运算符还是成员函数。

我认为修改容器的“修改器对象”方法应该在过去已经讨论过,因为它根本不是一个新想法。

总结一下,

  1. 是否可以使用返回值来模拟使用输出参数时会发生什么,特别是当输出是容器时?
  2. 如果没有,是否将这些支持添加到之前讨论的标准中?如果是,有什么问题?这是一个可怕的想法吗?

编辑

我上面的代码示例具有误导性。该函数f()可用于初始化局部变量,但也可用于修改在别处定义的现有变量。对于第一种情况,正如 Rakete1111 所提到的,当复制省略开始发挥作用时,按值返回没有问题。但是对于第二种情况,可能会有不必要的资源释放/获取。

4

2 回答 2

1

我认为您的“修饰符对象”从未被提出(AFAIK)。它永远不会进入标准。为什么?因为我们已经有办法摆脱昂贵的副本,那就是按值返回(加上编译器优化)。

在 C++17 之前,编译器被允许做几乎相同的事情。这种优化称为 (N)RVO,它在从函数返回(命名的)临时文件时优化掉副本。

auto a = f(other_arguments);

不会返回一个临时的,然后将其复制到a. 编译器将完全优化副本,这不是必需的。理论上,你不能假设你的编译器支持这个,但是三个主要的(clang、gcc、MSVC)支持,所以不用担心——我不知道 ICC 和其他的,所以我不能说。

因此,由于不涉及复制(或移动),因此使用返回值而不是输出参数不会造成性能损失(很可能,如果由于某种原因您的编译器不支持它,您将获得大部分的移动时间)。如果可能,您应该始终使用返回参数,并且仅在您测量到您获得明显更好的性能时才使用输出参数或其他一些技术。

于 2017-11-19T15:03:05.243 回答
-2

(编辑,根据评论)

你是对的,如果可能的话,你应该避免输出参数,因为使用它们的代码更难阅读和调试。

从 C++11 开始,我们有一个称为移动构造函数的特性(参见参考资料)。您可以std::move在所有原始和 STL 容器类型上使用。它是高效的(问题:复制和移动构造函数之间的效率差异),因为您实际上并没有复制变量的值。只有指针被交换。对于您自己的复杂类型,您可以编写自己的移动构造函数。另一方面,您唯一能做的就是返回对临时的引用,该引用具有未定义的行为,例如:

#include <iostream>

int &&add(int initial, int howMany) {
    return std::move(initial + howMany);
}

int main() {
    std::cout << add(5, 2) << std::endl;
    return 0;
}

它的输出可能是:7

您可以使用全局变量或静态变量来避免临时变量的问题,但这也很糟糕。@Rakete 是对的,没有实现它的好方法。

于 2017-11-19T14:52:10.713 回答