【问题标题】:Is using same parameter name and member name valid是否使用相同的参数名称和成员名称有效
【发布时间】:2014-01-04 02:15:03
【问题描述】:

这是否有效C++,假设我想将参数变量复制到成员变量:

struct Struct {
  Struct(const T& value) : value(value) {}
  T value;
};

(更新:它在 Visual Studio 中工作,但仍然可能依赖于编译器) (预期问题:你为什么需要这个?答案:宏观制作目的

【问题讨论】:

  • 如果你在构建时启用了很多警告(我认为在开发时应该是默认设置),会发生什么?如果您没有收到任何错误或警告,则说明一切正常。
  • 你知道你可以连接预处理器令牌吗?
  • @KerrekSB:是的,我知道,但是使用相同的名称会使宏更简洁。

标签: c++ variables constructor language-lawyer argument-passing


【解决方案1】:

毫无疑问,这是完全有效、符合标准的代码。

Struct(const T& value) : value(value) {}
                               ^^^^^ this is argument
                         ^^^^^ this is the member

现在的问题是:这是一种好的做法吗?在我看来,不。我更喜欢我的数据成员遵循不同但一致的命名约定,例如数据成员总是以_ 开头。所以我更喜欢这个:

Struct(const T& value) : _value(value) {}

_value 是数据成员。你可以遵循任何命名约定——只要确保你是一致的。

请注意,在您的代码变量、函数、类或任何标识符中,不应以双下划线开头,例如__value,或单下划线后跟大写字母,例如_Value——这些名称是保留的 用于实现。

【讨论】:

  • 不是为实现(->UB)保留带下划线前缀的名称吗?
  • @H2CO3: 不。如果你使用 single 下划线后跟 lower 大写字母,那完全没问题。 Names such as __value_Value 不允许,但是!
  • @H2CO3:它们仅保留用于全局命名空间。它们只是在其他范围内很丑陋,对于那些认为巴洛克曲线增强代码的人来说是完全有效的。
  • 出于这个原因,我更喜欢尾随 _,所以我不必记住这些规则。
  • @JohnBartholomew:名称包含两个连续的下划线,或以下划线和一个大写字母开头。
【解决方案2】:

这是有效的,但是,甚至需要问这个问题就证明了这一点,这可能会令人不快和困惑。

老生常谈的问题是为有点相同的东西想出两个不同的名字。

我喜欢这样做(但有些人讨厌它):

struct Struct {
  Struct(const T& newValue) : value(newValue) {}
  T value;
};

【讨论】:

    【解决方案3】:

    是的。它确实编译。对于编译器来说,value 是哪个没有歧义。

    #include <iostream>
    
    using namespace std;
    
    template <typename T>
    struct Struct {
        Struct(const T & value) : value(value) {}
        T value;
    };
    
    int main() {
    
        Struct<int> T(1);
        // your code goes here
        return 0;
    }
    

    http://ideone.com/gPyBK6

    但是在多次声明中,这对于程序员来说并不容易破译。它确实有效,因为对于编译器来说,参数掩盖了成员函数,所以value(value) 中的第二个value 是参数,但由于只有成员和祖先类可以在value(value) 的左侧,它确实指这里的会员。

    复杂到足以使调试和维护复杂化。

    【讨论】:

    • 它确实可以编译,但存在歧义(不是编译器,而是代码的未来读者)。
    • @jrok 我正在编辑中......任何时候你在初始化列表中看到value(value),一匹小马就死了。
    • 可以说,将 ivar 和初始化列表参数调用相同是一种不好的做法,但不幸的是,它是有效的。
    • @ZacHowland 是的,但这一事实并不能说明这个答案在技术上是不正确的。
    • @ZacHowland 信使正在从他的皮肉伤口中慢慢恢复:p
    【解决方案4】:

    这是有效的,但在某些圈子中是不明智的,包括我的圈子。

    从某种意义上说,成员变量将根据您的需要由参数正确设置是有效的。执行初始化列表后,该成员将被隐藏。对value 的任何引用都将访问该参数。这可能是件坏事。

    这是不明智的,原因有两个。首先,可维护性和混乱。参数和成员变量同名是不常见的。因此,大多数程序员将不得不停下来思考这意味着什么。毕竟,你做到了。请记住,代码首先是为程序员编写的,其次是为编译器编写的。易于理解的代码比难以理解的代码要好得多。在代码审查中,我会基于这些理由拒绝此代码。

    其次,成员隐藏在大多数情况下可能会成为问题。

    我建议提出一个合理的命名方案并坚持下去。 “Sane”意味着参数永远不能与成员变量同名。例如,在我的命名方案中,成员变量总是以m 开头——参数永远不会在前面。所以在这个方案中你的代码会变成:

    struct Struct {
      Struct(const T& value) : mValue(value) {}
      T mValue;
    };
    

    使用这个方案,没有人会对这里发生的事情感到困惑,也没有人需要问 StackOverflow“这是合法的吗?”

    【讨论】:

    • IIRC,成员不是被参数隐藏了,不是反过来吗?
    • 您确定存在名称隐藏问题吗?我认为12.6.2 paragraph 2 部分会阻止这种情况,还是我误解了?我认为expression-list 的外观规则隐含在类定义之外。我指的是这个draft
    【解决方案5】:

    有效。但是有一点警告:更改参数名称和代码是未定义的行为。

    template <typename T>
    struct Struct {
        Struct(const T & argument) : value(value) {}
        T value;
    };
    

    【讨论】:

      【解决方案6】:

      这是 C++ 标准所允许的,但请考虑在初始化成员后,您希望在函数中做更多工作的情况。比如用3代替一些更有意义的计算:

      class Foo
      {
      public:
          int bar;
          Foo(int bar) : bar(bar) { bar = 3; }
      };
      

      函数中的赋值改变了参数bar的值,而不是成员bar的值。这在您的示例中不会发生,因为您使用const 声明了参数。所以,如果你确定总是用const 声明参数,你就会受到保护。但是考虑一个更复杂的场景:

      class Foo
      {
      public:
          int bar;
          int baz;
          void AuxiliaryFunction() { bar = 3; }
          Foo(const int &bar) : bar(bar)
          {
              AuxiliaryFunction();
              baz = bar;
          }
      };
      

      在这个例子中,成员bar 通过构造函数中调用的另一个函数被赋予了一些值。那么赋值baz = bar;可能是想复制成员bar,但实际上复制的是参数bar

      因此,虽然这是合法的 C++,但应谨慎使用。

      【讨论】:

        【解决方案7】:

        这确实是有效的代码,与其他答案一样,我会警告您,应该非常小心地使用它,因为它可能会造成混淆并且可能会导致代码难以维护。

        那么为什么会这样呢?如果我们考虑您的构造函数:

        Struct(const T& value) : value(value) {}
                                 ^     ^
                                 1     2    
        

        12 在不同的范围内进行评估。所以我们需要查看draft C++ standard 部分12.6.2 Initializing bases and members 并查看一些语法:

        ctor-initializer:
            : mem-initializer-list 
        mem-initializer-list:
            mem-initializer ...opt
            mem-initializer , mem-initializer-list ...opt
        mem-initializer:
            mem-initializer-id ( expression-listopt )
            mem-initializer-id braced-init-list
        

        消化后我们看到1确实是一个mem-initializer-id,而2是一个expression-listopt,我们可以去第2 和 12 分别代表这些。第 2 段说:

        在 mem-initializer-id 中,在构造函数类的范围内查找初始的非限定标识符,如果在该范围内找不到,则在包含构造函数定义的范围内查找。 [...]

        所以1 将首先在课堂上查找,而我们可以从第 12 段中看到:

        mem-initializer 的表达式列表或花括号初始化列表中的名称在指定 mem-initializer 的构造函数的范围内进行评估。

        2 将在构造函数的范围内查找。所以1 将首先找到成员变量并停止查找,而2 将在构造函数中查找并找到参数。这也意味着,如果您想引用 expression-list 中的成员变量,则必须使用 this->

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-08-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多