让函数的input、output更"函数化"

前言

我们都知道函数的基本形式为:output f(input),且先按这种形式进行input与output的分析,我们的input与output可以有更好的设计方式,而我们的output是选择使用return by value还是return by reference也会有一定的思考,使得我们的函数更加"函数化"。

input

函数的input一般指通过传参给函数,这些参数一般是通过传递引用到const参数(const T&)来表示(当然基本类型直接传value也是ok的)。我们在函数里可以调用这个参数,但同时因为这个参数是const类型,所以不能进行修改,所以说const T&的input在函数中的作用只是一个“只读”的参数。
如果我们想让传入的参数同时还有output的能力,这样表达可能有些奇怪,换一句话说就是我们希望我们传入的同时具有被修改的能力,这样在该函数中修改后我们甚至都不用返回这个input的新值,就能在其他函数中调用该参数的最新的值,这里只需要使用T &就行了。

output

再回到我们的函数基本形式上来:Output f(const Input& input),Output是函数的返回类型;但正如我们前面所说的,如果我们想让参数同时具有output的能力,或许我们可以这样写:

void f(const Input& input, Output& output);

这样,我们的output参数在函数中被修改,自然也就不用再返回Output了,这样不是非常的好么?
先说一下这个新方法(return by reference)的一些弊端:
1.不太符合语言的自然性
输出参数之所以是output,就因为它是输出(output类型)的呀.......假如我们就按刚才提到的方法做,我们看看我们在其他函数中应该怎么写:

Output output;
.......
f(input,output);

如果我们再加上几个output:

Output output1,output2,output3,output4;

f(input,output1);
f(input,output2);
f(input,output3);
g(input,output4);

相比我们如果用Output f(const Input& input)来表达:

Output output = f(input);

return by reference显得不自然,如果出现在工程项目里,一句f(input,output)可能都会让你忘记这个函数是想干什么,这个output又是什么东西;所以第一点,这个语法不够自然。
2.我们并不能确保我们的函数是真的将output赋予新值了
我们通过return by value还是可以较为方便的通过检测机制看到这个值是否发生变化的,但是如果是return by reference,我们实际上并不能确切的得知output是否确实发生改变了(在函数中再加入一个检测机制并不是一个好方法.......)
虽然利用return来返回类型确实不是被很多人使用,但是除非是真的无法用return来返回output,我一般都会建议利用return来返回,下面说说使用Output f(const Input& input)的好处:

性能

一部分人可能会认为通过return返回,需要拷贝赋值等等会耗费空间的举措,但是C++中已经有了很多语言机制去消除returning by value的copy,首要提及的自然是RVO(Return Value Optimisation):在这里举个例子吧,比如下面这个程序:

T f()
{
    ....
    return T(constructor arguments);
}

T t=f();

理论上,会创建三个T对象,第一个是在T f()的return中的构造对象,第二个是拷贝了上一个对象的临时对象,第三个则是将临时对象copy给t。但是实际上,RVO让我们的编译器移除了第一个与第二个对象,允许我们直接使用传递到f中的构造函数的参数来初始化t。有兴趣的可以参考一下这篇[博客(https://www.cnblogs.com/xkfz007/archive/2012/07/21/2602110.html)]。当然我们也有std::move移动构造,比如我们返回一个STL的容器的时候并不是拷贝而是会移动它,由于优化,与拷贝所耗费时间都是差不多的。

差错处理

正如我们关于return by reference的第二个缺陷,我们在return by value会有很好的办法去处理他:

bool f(const Input& input, Output& output);
Output output;
bool success = f(input, output);
if (success)
{
   // use output ...
}

通过返回类型我们可以很好的知道是否执行成功,在网络编程中,我i们的基本函数bind()、connect()、listen()、socket()等等都是通过这种返回形式来执行差错处理的。
而在C++17标准中有了一个std::optional的方法,如果函数执行成功我们将得到正确的output,如果没有我们将返回一个空的值(empty),更方便我们的差错处理。

返回多个类型

想一想如果我们利用return by reference返回多个类型,该怎么写?

Output1 output1;
Output2 output2;
void f(const Input& intput, Output1& output1, Output2& output2);

当然我们也可以定义一个结构体,将所有数据存进来再通过return by reference返回:

struct Outputs
{
   Output1 output1;
   Output2 output2;
};

Outputs f(const Input& input);

但是如果我们return by value,就可以通过tuple来实现:

std::tuple<Output1, Output2> f(const Input& input);
	
Output1 output1;
Output2 output2;
std::tie(output1, output2) = f(inputs);

而在C++标准中我们可以通过auto直接返回多个参数:

auto [output1, output2] = f(const Input& input);

支持了这种结构化的绑定,会更加方便。

结论

或许用"该选择return by value还是return by reference"可能更好一些。自己的结论就是不到无法使用return by value的时候,出于对代码的清晰性和表达性的考量,尽量别使用return by reference。

原文地址:https://www.cnblogs.com/yunlambert/p/10061325.html