C++ 输出参数并通过引用传递

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4028413/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 14:19:40  来源:igfitidea点击:

Out parameters and pass by reference

c++pointersreferencecoding-style

提问by Martin York

I have joined a new group that has coding guidelines that (to me) seem dated.

我加入了一个新的小组,该小组的编码指南(对我来说)似乎过时了。

But just rallying against the machine without valid backup is not going to get me anywhere.
So I am turning to SO to see if we can up with rational reasons for/against (hey I may be wrong in my option so both sides of the argument would be appreciated).

但是,仅仅在没有有效备份的情况下对抗机器不会让我有任何进展。
所以我转向 SO,看看我们是否可以提出支持/反对的合理理由(嘿,我的选择可能是错误的,因此争论的双方都会受到赞赏)。

The guideline that is up for argument is:

可供论证的准则是:

Tip: Use pointers instead of references for return arguments.

void Func1( CFoo &Return );  // bad  
void Func2( CFoo *pReturn ); // good  

Justification:
When you use a reference, it looks the same as a value. The caller may be surprised that his value has been changed after calling the function. The callee may innocently modify the value without meaning to affect the caller's value. By using a pointer, it is clear to both the caller and callee that the value can be changed. Using references can be particularly misleading in code reviews.

提示:使用指针而不是引用作为返回参数。

void Func1( CFoo &Return );  // bad  
void Func2( CFoo *pReturn ); // good  

理由:
当您使用引用时,它看起来与值相同。调用者可能会惊讶于调用函数后他的值发生了变化。被调用者可以无意地修改该值,而无意影响调用者的值。通过使用指针,调用者和被调用者都清楚该值可以更改。在代码中使用引用尤其容易产生误导。

采纳答案by James McNellis

When you use a reference, it looks the same as a value.

使用引用时,它看起来与值相同。

Only if you really aren't paying attention to what you are doing. Ok, sometimes that happens, but really... no amount of coding standards can correct for people not paying attention or not knowing what they are doing.

只有当你真的没有注意你正在做的事情时。好吧,有时会发生这种情况,但实际上......没有多少编码标准可以纠正人们不注意或不知道他们在做什么。

The caller may be surprised that his value has been changed after calling the function.

调用者可能会惊讶于调用该函数后他的值发生了变化。

If you are surprised by what happens when you call a function, then the function is poorly documented.

如果您对调用函数时发生的事情感到惊讶,那么该函数的文档记录很差。

Given a function's name, its parameter list, and perhaps some very brief and descriptive documentation, it should be eminently clear what the function does and what its observable side effects are (including whether any arguments are modified).

给定一个函数的名称、它的参数列表,也许还有一些非常简短和描述性的文档,应该非常清楚该函数的作用及其可观察到的副作用是什么(包括是否修改了任何参数)。

The callee may innocently modify the value without meaning to affect the caller's value.

被调用者可以无意地修改该值,而无意影响调用者的值。

If the function is const correct, then this isn't a problem. If the function isn't const correct, then it should be made const correct, if you can (retroactively making code const correct can be an absolute beating).

如果函数是 const 正确的,那么这不是问题。如果函数不是 const 正确的,那么它应该是 const 正确的,如果可以的话(追溯使代码 const 正确可能是一个绝对的打击)。

This rationale doesn't make much sense, though: when you are actually writing the code for a function, you should be able to see the declarations of the parameters. If the function is so long that you can't, it's time for refactoring.

但是,这个基本原理没有多大意义:当您实际为函数编写代码时,您应该能够看到参数的声明。如果函数太长以至于你不能,那就是重构的时候了。

By using a pointer, it is clear to both the caller and callee that the value can be changed.

通过使用指针,调用者和被调用者都清楚该值可以更改。

This is not entirely correct. A function can take a pointer to const object, in which case the object cannot be changed.

这并不完全正确。函数可以使用指向 const 对象的指针,在这种情况下,该对象无法更改。

Using references can be particularly misleading in code reviews.

在代码中使用引用尤其容易产生误导。

Only if the people doing the code reviews don't know what they are doing.

只有在进行代码的人不知道他们在做什么的情况下。



All of that is well and good, but why should pass-by-reference be used instead of pass-by-pointer? The most obvious reason is that a reference cannot be null.

所有这些都很好,但是为什么要使用引用传递而不是指针传递呢?最明显的原因是引用不能为 null

In a function that takes a pointer, you have to check that the pointer is not null before you use it, at least with a debug assertion. During a proper code review you have to analyze more code to be sure that you don't accidentally pass a null pointer to a function that doesn't expect one. I've found that it takes much longer to review functions that take pointer arguments for this very reason; it's so much easier to get it wrong when using pointers.

在使用指针的函数中,您必须在使用指针之前检查指针是否为空,至少使用调试断言。在适当的代码期间,您必须分析更多代码以确保您不会意外地将空指针传递给不期望空指针的函数。我发现由于这个原因,采用指针参数的函数需要更长的时间;使用指针时更容易出错。

回答by Greg Hewgill

It seems to me that the proper use of constwould (mostly) eliminate the need for that tip. The part that still seems useful is when reading caller code, seeing:

在我看来,正确使用const将(主要)消除对那个提示的需要。看起来仍然有用的部分是在阅读调用者代码时,看到:

Func1(x);

it isn't quite clear what is being done with x(particularly with a nondescript name like Func1). Instead using:

目前还不清楚正在做什么x(尤其是像 那样不起眼的名字Func1)。而是使用:

Func2(&x);

with the above convention, indicates to the caller that they should expect xto be modified.

使用上述约定,向调用者表明他们应该期望x被修改。

回答by Peter Alexander

While I wouldn't use the tip's advice myself, the justification is valid, which is why languages like C# introduced the outand refkeywords for use at the call site.

虽然我自己不会使用提示的建议,但理由是有效的,这就是为什么像 C# 这样的语言引入了outref关键字以在调用站点上使用。

The best argument I can come up for against it is this: instead of requiring people to use pointers, you should instead require that people write function names that reflect what the function does. When I call std::swap, I know it's going to change the value of the arguments because the name implies that. On the other hand, if I were to call a function getSize, I wouldn't expect that to modify any arguments.

我能提出的最佳论点是:与其要求人们使用指针,不如要求人们编写反映函数功能的函数名。当我调用 时std::swap,我知道它会改变参数的值,因为名称暗示了这一点。另一方面,如果我要调用一个函数getSize,我不希望它修改任何参数。

回答by Andy Thomas

If you have not already, buy a copy of Herb Sutter and Andrei Alexandrescu's "C++ Coding Standards: 101 Rules, Guidelines and Best Practices." Read it. Recommend it to your co-workers. It's a good base for a local coding style.

如果您还没有,请购买 Herb Sutter 和 Andrei Alexandrescu 的“C++ 编码标准:101 条规则、指南和最佳实践”的副本。阅读。推荐给你的同事。这是本地编码风格的良好基础。

In Rule 25, the authors recommend:

在规则 25 中,作者建议:

"Prefer passing by reference if the argument is required and the function won't store a pointer to it or otherwise affect its ownership. This states that the argument is required and makes the caller responsible for providing a valid object."

“如果需要参数并且函数不会存储指向它的指针或以其他方式影响其所有权,则更喜欢通过引用传递。这表明该参数是必需的,并使调用者负责提供有效的对象。”

"Argument is required" means NULL is not a valid value.

“需要参数”表示 NULL 不是有效值。

One of the most frequent causes of defects is accidental de-referencing of null pointers. Using references instead of pointers in these cases can eliminate these at compile-time.

导致缺陷的最常见原因之一是意外取消引用空指针。在这些情况下使用引用而不是指针可以在编译时消除这些。

So you have a trade-off -- eliminate a frequent source of errors, or ensure understandability of calling code by means other than the function name. I personally lean toward eliminating risk.

因此,您需要进行权衡——消除常见的错误源,或确保通过函数名称以外的方式调用代码的可理解性。我个人倾向于消除风险。

回答by Mark Ransom

Coding standards are based on habits as much as common sense. Some of your coworkers may rely on years of ingrained assumptions that a parameter not passed by pointer won't change - have pity on them.

编码标准基于习惯和常识。你的一些同事可能依赖多年根深蒂固的假设,即不是通过指针传递的参数不会改变——可怜他们。

The important part of coding standards is not that they're optimal, but that they're adhered to by everybody so that there's some consistency to the body of code.

编码标准的重要部分不是它们是最佳的,而是每个人都遵守它们,以便代码体具有一定的一致性。

回答by Mark Ransom

If they really want explicit mention of out parameters at the call site, they should actually require that instead of hacking around it by trying to make pointers mean something they don't. Pointers don't imply modification any more than references do, and it's not uncommon to pass pointers for non-modified objects.

如果他们真的想在调用站点明确提及 out 参数,他们实际上应该要求这样做,而不是通过试图使指针表示他们没有的意思来绕过它。指针并不比引用更意味着修改,并且为未修改的对象传递指针并不少见。

One potential way to express out parameters explicitly:

显式表达参数的一种潜在方式:

template<class T>
struct Out {
  explicit Out(T& obj) : base(obj) {}
  T& operator*() { return base; }
  T* operator->() { return &base; }
private:
  T& base;
};
template<class T>
Out<T> out(T& obj) {
  return Out<T>(obj);
}

void f(Out<int> n) {
  ++*n;
}

int main() {
  int n = 3;
  f(out(n));
  cout << n << '\n';
}

And as a temporary measure until they change old code to this, you can make the Out convertible to a pointer and/or reference:

作为临时措施,直到他们将旧代码更改为此,您可以将 Out 转换为指针和/或引用:

// in class definition
operator T*() { return &base; }
operator T&() { return base; }

// elsewhere
void old(int *p);
void g() {
  int n;
  old(out(n));
}

I went ahead and wrote the various classes required for this, and for in-out parameters, in a way that should degrade nicely. I doubt I'll be using that conventionany time soon (in C++, at least), but it'll work for anyone that wants to make call sites explicit.

我继续编写了为此所需的各种类,以及输入输出参数,以一种应该很好地降级的方式。我怀疑我很快就会使用该约定(至少在 C++ 中),但它适用于想要明确调用站点的任何人。

回答by peterchen

I found there are two schools of though about this:

我发现有两所学校关于这个:

  • (a) use a pointer to show a parameter may be modified
  • (b) use a pointer if and only if the parameter may be null.
  • (a) 使用指针显示一个参数可能被修改
  • (b) 当且仅当参数可能为空时才使用指针。

I agree with your motivation for (a): when reading code, you can't know all declarations, even if a mouseover gives you the declaration of the function. Mousing over hundreds of functions in thousands of lines just takes time.

我同意你 (a) 的动机:在阅读代码时,你不可能知道所有的声明,即使鼠标悬停给你函数的声明。将数千行中的数百个函数用鼠标移动只是需要时间。

I certainly see a problem here if you mix in and out parameters:

如果您混合输入和输出参数,我肯定会在这里看到一个问题:

bool GetNext(int index, Type & result);

A call to this fuinction would look like this:

对此功能的调用如下所示:

int index = 3;
Type t; 
if (!GetNext(index, t)) 
  throw "Damn!";

In that example, the call itself is fairly obvious, to potentially modify t. But what about index? Maybe GetNextincrements the index, so you always get the next item, without the callee needing to keep caller state?

在那个例子中,调用本身是相当明显的,潜在地修改t. 但是呢index?也许GetNext增加索引,所以你总是得到下一个项目,而被调用者不需要保持调用者状态?

Which usually raises the reply Then the method should be GetNextAndIncrementIndex, or you should use an iterator anyway. I bet these people never had to debug code written by electrical engineers that still think Numerical Recipesis the Holy Grail of programming.

这通常会引发回复那么方法应该是GetNextAndIncrementIndex,或者无论如何你应该使用迭代器。我敢打赌,这些人从来不需要调试电气工程师编写的代码,他们仍然认为数字食谱是编程的圣杯。

HowverI still tend to (b): simply because the problem can be avoided for new code being written, and "may be null or not" is usually the more common problem.

但是我仍然倾向于(b):仅仅因为可以避免编写新代码的问题,并且“可能为空或不为空”通常是更常见的问题。

回答by Martin York

The justification is logically true.
It may surprise coders that the value has changed (because they thought the value was being passed by value).

理由在逻辑上是正确的。
编码人员可能会惊讶于该值已更改(因为他们认为该值是按值传递的)。

But does logically true provide any meaning in this context.
So the value may change. How does this affect the correctness of the code?
Apart from it may print out a different value then an illogical human expects, but the code is doing what it is supposed to be doing and the compiler is enforcing constraints.

但在这种情况下,逻辑上确实提供了任何意义。
所以价值可能会改变。这如何影响代码的正确性?
除了它可能会打印出一个与不合逻辑的人所期望的不同的值,但是代码正在做它应该做的事情并且编译器正在执行约束。

回答by Dima

I would disagree with this guideline. The confusion mentioned in the justification can be easily resolved by making sure the code is const-correct. If you are passing an input parameter to a function by reference, then it should be a constreference. If the reference is not const, that is an indication that it is an output parameter, whose value may be changed by the function.

我不同意这个指导方针。通过确保代码是常量正确的,可以轻松解决理由中提到的混淆。如果您通过引用将输入参数传递给函数,那么它应该是一个const引用。如果引用不是const,则表明它是一个输出参数,其值可能会被函数更改。

Furthermore, when you pass a pointer to a function, rather than a reference, that instantly raises a question about whether or not this is a pointer to dynamically allocated memory, and whether or not it should be freed. Using a reference removes the temptation to call delete.

此外,当您传递一个指向函数的指针而不是一个引用时,这会立即引发一个问题,即这是否是一个指向动态分配的内存的指针,以及它是否应该被释放。使用引用可以消除调用delete.

There are times when passing a pointer is appropriate, such as when it actually is a pointer to a dynamically allocated object or array, or when it makes sense for it to be null. Although, you should prefer a smart pointer in such cases. In all other cases a reference is better, IMHO.

有时传递指针是合适的,例如当它实际上是一个指向动态分配的对象或数组的指针时,或者当它为空时。不过,在这种情况下,您应该更喜欢智能指针。在所有其他情况下,参考更好,恕我直言。

回答by justin

i recommend:

我建议:

  • pass by reference (do not pass by pointer)
  • pass by const reference wherever possible (assuming you've used const correctly throughout your codebase)
  • place arguments/parameters which mutate at the beginningof the list
  • label the function appropriately
  • label the argument appropriately (and create methods/functions with detailed and descriptive names and few arguments)
  • document the result
  • if multiple arguments/parameters mutate, consider creating a simple class which holds these arguments (even if by reference themselves)
  • if they still can't function (sic) without visual and documented cues, create a lightweight template container object for the parameter which mutates, which is then passed to the method or function
  • 通过引用传递(不通过指针传递)
  • 尽可能通过 const 引用传递(假设您在整个代码库中都正确使用了 const)
  • 将发生变化的参数/参数放在列表的开头
  • 适当地标记函数
  • 适当地标记参数(并创建具有详细描述性名称和少量参数的方法/函数)
  • 记录结果
  • 如果多个参数/参数发生变化,请考虑创建一个包含这些参数的简单类(即使通过引用本身)
  • 如果它们在没有视觉和文档提示的情况下仍然无法运行(原文如此),请为发生变异的参数创建一个轻量级模板容器对象,然后将其传递给方法或函数