使用引用的Void函数与在C++中返回值的函数相比

Void functions that take a reference vs functions that return a value in C++

本文关键字:函数 返回值 C++ 引用 Void      更新时间:2023-10-16

在C++中,通过将对变量的引用传递到"initialization"函数来初始化变量是一种好的做法吗?或者,换言之,编写以这种方式运行的函数(即更新在其他地方创建的变量)是一种很好的做法?在我的编程入门课(用Java教授)中,我们被教导将这样的方法写成static,并给它们显式的返回值。但我从一些示例中注意到,一些C++程序员在没有显式初始化的情况下声明他们的变量,将它们交给某个函数,然后继续在程序中使用它们。这两种风格都有优点/缺点吗?(我把纯粹OO的东西,比如成员函数和变量排除在这个问题之外——这不仅仅是关于使用方法更新对象的状态。我在C++中看到过在类之外这样做)。

我写了几行快速的代码来说明我的意思。第一个函数genName()是我熟悉的样式。第二,gen_name()是我很好奇的那种。

string genName() {
    string s = "Jack" ;
    return s ;
}
void gen_name(string & s) {
    s = "Jill" ;
}
int main(int argc, const char * argv[]) {
    string name1 = genName() ;
    string name2 ;
    gen_name(name2) ;
    cout << name1 << endl ;
    cout << name2 << endl ;
    return 0;
}

在C++98中,引用初始化风格曾经流行于初始化复杂的数据类型,因为C++98没有提供移动构造函数,并且返回值优化尚未普遍实现。

例如,创建并返回大向量的函数会受到反对,因为它实际上会创建一个临时向量,该向量(缺乏可靠实现RVO的编译器)会被复制到目标向量及其所有元素。这种不必要的本地分配和复制导致一些程序员和样式指南建议处处使用引用样式进行初始化。现代C++通过move构造函数和std::move解决了这一问题,因此引用模式的初始化可以退役。

第二个选项之所以流行,是因为复制对象(如std::stringstd::map等)的开销很高。如果复制这些对象,不仅会有深度复制元素的开销,还会有代价高昂的堆分配开销。

话虽如此,在C++11中,由于移动语义,很多这样的东西都消失了,它允许我们做一些以前做不到的事情。

例如,如果您希望name是一个const对象,这可能很有用。

const std::string name = []() {
  std::string name;
  /* Fill in name. */
  return name;
}();

但是,请注意,在某些情况下,通过引用进行初始化仍然很有用。例如,以下代码:

for (int i = 0; i < N; ++i) {
  const std::string name = gen_name(i);
  /* Use name here. */
}  // for

尽管如果我们知道不会修改const,那么添加它会很好,但就性能而言,以下操作会更快。

std::string name;
for (int i = 0; i < N; ++i) {
  gen_name(i, name);
  /* Use name here. */
}  // for

编辑:

我之所以指出,在某些情况下,通过引用进行初始化可能更可取,是因为有时我们可以重用在循环中获得的资源。在上面的例子中,我们可以简单地在开始时进行单个堆分配,并保持重用相同的空间,而不是在每次迭代中构建一个新的实例std::string,这将导致每次迭代中的堆分配。

通过引用传递会降低代码的可读性,而且代码读起来比写起来更难。

C++中最重要的原因是,在Java中,每个对象都是引用,所以结果很便宜。在C++中,可能会返回整个结构。少量的复制开销。

但参考参数也有好处:

多个结果否则需要一个额外的结果类型作为多个结果值的容器。

void divideAndRemainder(int p, int q, int& d, int& r)

多个结果也需要准备输入。

void swapVariables(int& a, int& b);

字段别名填充该字段或该字段/变量。

struct link {
    struct link* next;
    int value;
}
// Ordered list insert, not possible like this in Java:
// Read "struct link*&", but for clarity I use explicit dereferencing here:
// *list.
void insert(struct link** list, int value) {
    while (*list && value < (*list)->value) {
        list = &(*list)->next;
    }
    struct link* next = *list;
    *list = new struct link();
    (*list)->value = value;
    (*list)->next = next;
}

(注意,我现在是一个根深蒂固的Java程序员。)