代码之家  ›  专栏  ›  技术社区  ›  Loki Astari

输出参数和按引用传递[关闭]

  •  37
  • Loki Astari  · 技术社区  · 14 年前

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


    因此,我转而这么做是为了看看我们是否能够提出合理的理由来支持/反对(嘿,我的选择可能是错误的,所以双方的论点都会得到赞赏)。

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

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

    正当理由:

    10 回复  |  直到 14 年前
        1
  •  35
  •   James McNellis    14 年前

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

    调用方可能会惊讶于在调用函数后其值已更改。

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

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

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

    如果函数是const correct,那么这不是问题。如果函数不是const correct,那么应该将其设置为const correct,如果可以的话(追溯地设置code const correct可能是一个绝对的打击)。

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

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

    在代码评审中使用引用可能会特别误导。


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

        2
  •  18
  •   Greg Hewgill    14 年前

    在我看来 const 会(基本上)消除对小费的需求。在阅读调用者代码时,仍然有用的部分如下:

    Func1(x);
    

    x (尤其是一个不起眼的名字,比如 Func1 ). 而是使用:

    Func2(&x);
    

    根据上述约定,向调用者表明他们应该期望

        3
  •  11
  •   Peter Alexander    14 年前

    虽然我自己不会使用提示的建议,但理由是正确的,这就是为什么像C语言这样的语言引入了 out ref 用于呼叫站点的关键字。

    我能提出的最好的反对理由是:你不应该要求人们使用指针,而应该 . 当我打电话 std::swap ,我知道它将更改参数的值,因为名称暗示了这一点。另一方面,如果我调用一个函数 getSize

        4
  •  10
  •   Andy Thomas    14 年前

    如果你还没有,请购买一本Habor萨特和Andrei Alexandrescu的“C++编码标准:101条规则、指南和最佳实践”。把它推荐给你的同事。这是一个很好的地方编码风格的基础。

    在第25条中,提交人建议:

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

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

    因此,您有一个折衷方案——消除经常出现的错误源,或者确保通过函数名以外的方法调用代码的可理解性。我个人倾向于消除风险。

        5
  •  7
  •   Mark Ransom    14 年前

    编码标准的重要部分不是它们是最优的,而是每个人都遵守它们,这样代码体就有了一些一致性。

        6
  •  7
  •   Roger Pate Roger Pate    14 年前

    如果他们真的希望在调用站点明确地提到out参数,那么他们应该实际地要求这样做,而不是试图使指针具有他们不具有的含义,从而绕过它。指针并不意味着修改,而与引用一样,为未修改的对象传递指针并不少见。

    显式表示参数的一种可能方法是:

    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';
    }
    

    // in class definition
    operator T*() { return &base; }
    operator T&() { return base; }
    
    // elsewhere
    void old(int *p);
    void g() {
      int n;
      old(out(n));
    }
    

    我继续写了这个和输入输出参数所需的各种类,以一种可以很好地降级的方式。我怀疑我会用 that convention

        7
  •  2
  •   peterchen    14 年前

    我发现有两种说法:

    • (a) 使用指针显示可以修改的参数
    • (b) 当且仅当参数可以为空时使用指针。

    我同意您(a)的动机:在阅读代码时,您不可能知道所有的声明,即使mouseover给了您函数的声明。在数千行中移动数百个函数只需要时间。

    如果你把输入和输出参数混合在一起,我肯定会看到一个问题:

    bool GetNext(int index, Type & result);
    

    调用此函数将如下所示:

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

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

    那么方法应该是 GetNextAndIncrementIndex ,或者无论如何都应该使用迭代器 数值分析方法库 是编程的圣杯。

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

        8
  •  1
  •   Loki Astari    14 年前

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

    但在逻辑上,它确实提供了这方面的任何意义。
    所以这个值可能会改变。这如何影响代码的正确性?

        9
  •  1
  •   justin    14 年前

    我建议:

    • 尽可能传递const引用(假设您在整个代码库中正确使用了const)
    • 放置在 名单的
    • 适当地标记函数
    • 适当地标记参数(并使用详细的描述性名称和少量参数创建方法/函数)
    • 记录结果
    • 如果多个参数/参数发生变化,请考虑创建一个包含这些参数的简单类(即使通过引用本身也是如此)
        10
  •  1
  •   Dima    14 年前

    我不同意这条准则。通过确保代码常量正确,可以很容易地解决理由中提到的混淆。如果通过引用将输入参数传递给函数,则它应该是 const 参考资料。如果引用不是 ,这表示它是一个输出参数,其值可以由函数更改。

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