为什么在C ++类中使用成员变量的前缀

时间:2009-08-04 15:25:46

标签: c++ coding-style naming-conventions

许多C ++代码使用语法约定来标记成员变量。常见的例子包括

    公共成员的
  • m_ memberName (根本使用公共成员)
  • 私人会员或所有会员的
  • _ memberName

其他人尝试在使用成员变量时强制使用this-> 成员

根据我的经验,大多数较大的代码库都无法一致地应用这些规则。

在其他语言中,这些惯例远没有那么广泛。我只偶尔在Java或C#代码中看到它。我想我从未在Ruby或Python代码中看到它。因此,似乎有一种趋势,即更多现代语言不对成员变量使用特殊标记。

这个约定今天在C ++中是否仍然有用,或者它只是一个时代错误。特别是因为它在库之间使用不一致。没有其他语言显示没有成员前缀可以做到吗?

29 个答案:

答案 0 :(得分:219)

答案 1 :(得分:106)

我通常不会为成员变量使用前缀。

我以前使用m前缀,直到有人指出“C ++已经有成员访问的标准前缀:this->

这就是我现在使用的东西。也就是说,当存在歧义时,我添加this->前缀,但通常不存在歧义,我可以直接引用变量名。

对我而言,这是两全其美的。我有一个我可以在需要时使用的前缀,我可以随时将其留下。

当然,明显的反驳是“是的,但是你不能一眼就看出一个变量是否是一个类成员”。

我说“那么什么?如果你需要知道,你的班级可能有太多的状态。或者功能太大而复杂”。

在实践中,我发现这非常有效。作为额外的奖励,它允许我轻松地将局部变量提升为类成员(或其他方式),而无需重命名。

最重要的是,它是一致的!我不需要做任何特别的事情或记住任何保持一致性的约定。


顺便说一句,不应为您的班级成员使用前导下划线。您会对实施保留的名称感到不安。

标准保留所有名称以双下划线或下划线开头,后跟大写字母。它还保留所有名称,以全局命名空间中的单个下划线开头。

因此,具有前导下划线后跟小写字母的类成员是合法的,但是对于以大写字母开头的标识符,或者以其他方式违反上述规则之一,您迟早会做同样的事情。

因此,避免引导下划线更容易。如果要在变量名中对范围进行编码,请使用后缀下划线或m_或仅m前缀。

答案 2 :(得分:44)

使用前导下划线时必须小心。保留一个单词中大写字母前的前导下划线。 例如:

_Foo

_L

时的所有保留字

_foo

_l

不是。在其他情况下,不允许使用小写字母前导下划线。在我的特定情况下,我发现_L碰巧被Visual C ++ 2005保留,并且碰撞产生了一些意想不到的结果。

我很清楚标记局部变量的用处非常有用。

以下是有关保留标识符的链接: What are the rules about using an underscore in a C++ identifier?

答案 3 :(得分:32)

我更喜欢后缀下划线,例如:

class Foo
{
   private:
      int bar_;

   public:
      int bar() { return bar_; }
};

答案 4 :(得分:19)

最近我一直倾向于选择m_前缀而不是没有前缀,原因并不是标记成员变量很重要,但它避免了歧义,比如你有代码:

void set_foo(int foo) { foo = foo; }

原因不起作用,只允许一个foo。所以你的选择是:

  • this->foo = foo;

    我不喜欢它,因为它会导致参数阴影,您不再可以使用g++ -Wshadow警告,也可以更长时间键入m_。当您拥有int foo;int foo();时,您还会遇到变量和函数之间的命名冲突。

  • foo = foo_;foo = arg_foo;

    已经使用了一段时间,但它使得参数列表难看,文档不应该在实现中处理名称歧义。此处也存在变量和函数之间的命名冲突。

  • m_foo = foo;

    API文档保持干净,您不会在成员函数和变量之间出现歧义,而是更短的键入this->。唯一的缺点是它会使POD结构变得丑陋,但是由于POD结构首先不会出现名称歧义,因此不需要将它们与它们一起使用。拥有独特的前缀也使一些搜索和替换操作更容易。

  • foo_ = foo;

    m_的大多数优点都适用,但我出于审美原因拒绝它,尾随或前导下划线只会使变量看起来不完整和不平衡。 m_看起来更好。使用m_也更具扩展性,因为您可以将g_用于全局变量,将s_用于静态变量。

PS:你没有在Python或Ruby中看到m_的原因是因为两种语言都强制执行自己的前缀,Ruby使用@作为成员变量,Python需要self.

答案 5 :(得分:10)

我不能说它是多么广泛,但是就个人而言,我总是(并且总是)以'm'为我的成员变量加前缀。 E.g:

class Person {
   .... 
   private:
       std::string mName;
};

这是我使用的唯一形式的前缀(我非常反匈牙利符号)但多年来它一直保持着我的优势。顺便说一句,我通常不赞成在名称中使用下划线(或其他任何地方),但确实为预处理器宏名称设置了一个例外,因为它们通常都是大写的。

答案 6 :(得分:10)

通过成员函数阅读时,了解每个变量的“拥有”对于理解变量的含义是绝对必要的。在这样的函数中:

void Foo::bar( int apples )
{
    int bananas = apples + grapes;
    melons = grapes * bananas;
    spuds += melons;
}

...很容易看到苹果和香蕉的来源,但是葡萄,甜瓜和土豆呢?我们应该查看全局命名空间吗?在课堂宣言中?变量是此对象的成员还是此对象的类的成员?如果不知道这些问题的答案,就无法理解代码。而在一个更长的函数中,即使是像苹果和香蕉这样的局部变量的声明也会在随机播放中丢失。

为全局变量,成员变量和静态成员变量(可能分别为g_,m_和s_)预先设置一致标签,可以立即澄清情况。

void Foo::bar( int apples )
{
    int bananas = apples + g_grapes;
    m_melons = g_grapes * bananas;
    s_spuds += m_melons;
}

这些可能需要一些人开始习惯 - 但是编程中的内容不是吗?有一天甚至{和}看起来很怪异。一旦你习惯了它们,它们就会帮助你更快地理解代码。

(使用“this-&gt;”代替m_是有意义的,但更加冗长且具有视觉上的破坏性。我不认为它是标记成员变量的所有用途的好方法。)< / p>

对上述论点的可能反对意见是将参数扩展为类型。知道变量的类型“对于理解变量的含义是绝对必要的”也可能是真的。如果是这样,为什么不为每个标识其类型的变量名称添加前缀?有了这个逻辑,你最终会得到匈牙利符号。但是很多人发现匈牙利的记谱法很费劲,很丑陋,也没有帮助。

void Foo::bar( int iApples )
{
    int iBananas = iApples + g_fGrapes;
    m_fMelons = g_fGrapes * iBananas;
    s_dSpuds += m_fMelons;
}

匈牙利确实告诉我们一些关于代码的新内容。我们现在明白Foo :: bar()函数中有几个隐式转换。现在代码的问题在于匈牙利前缀添加的信息的值相对于视觉成本较小。 C ++类型系统包含许多功能,可以帮助类型一起工作或引发编译器警告或错误。编译器可以帮助我们处理类型 - 我们不需要注释。我们可以很容易地推断出Foo :: bar()中的变量可能是数字的,如果这就是我们所知道的,这对于获得对函数的一般理解是足够好的。因此,知道每个变量的精确类型的价值相对较低。然而,像“s_dSpuds”(甚至只是“dSpuds”)这样的变量的丑陋是很棒的。因此,成本效益分析拒绝匈牙利表示法,而g_,s_和m_的好处压倒了许多程序员眼中的成本。

答案 7 :(得分:7)

成员前缀的主要原因是区分成员函数local和具有相同名称的成员变量。如果您使用具有该东西名称的getter,这将非常有用。

考虑:

class person
{
public:
    person(const std::string& full_name)
        : full_name_(full_name)
    {}

    const std::string& full_name() const { return full_name_; }
private:
    std::string full_name_;
};

在这种情况下,成员变量不能被称为full_name。您需要将成员函数重命名为get_full_name()或以某种方式修饰成员变量。

答案 8 :(得分:6)

我不认为一种语法比另一种语法具有实际价值。如你所提到的,这一切都归结为源文件的统一性。

我发现这些规则很有趣的唯一一点就是当我需要两个名为identicaly的东西时,例如:

void myFunc(int index){
  this->index = index;
}

void myFunc(int index){
  m_index = index;
}

我用它来区分这两者。此外,当我打包调用时,例如来自Windows Dll,来自Dll的 RecvPacket(...)可能会被包含在我的代码中的 RecvPacket(...)中。在这些特殊情况下,使用像“_”这样的前缀可能会使两者看起来很相似,很容易识别哪个是哪个,但编译器不同

答案 9 :(得分:6)

有些回复侧重于重构,而不是命名约定,作为提高可读性的方法。我觉得不能替换另一个。

我认识程序员对使用本地声明感到不舒服;他们更喜欢将所有声明放在一个块的顶部(如在C中),因此他们知道在哪里找到它们。我已经发现,在作用域允许的情况下,声明首次使用它们的变量会减少我花时间向后查找声明的时间。 (即使是小函数,这对我也是如此。)这使我更容易理解我正在看的代码。

我希望这与成员命名约定有何关系是明确的:当成员统一为前缀时,我永远不必回头看;我知道甚至不会在源文件中找到声明。

我确信我没有开始喜欢这些风格。然而,随着时间的推移,在一直使用它们的环境中工作,我优化了我的想法以利用它们。我认为,鉴于使用一致,许多目前感到不舒服的人也可能会更喜欢他们。

答案 10 :(得分:5)

那些惯例就是这样。大多数商店使用代码约定来简化代码可读性,因此任何人都可以轻松查看一段代码并快速解读公共和私人成员之间的事物。

答案 11 :(得分:5)

  

其他人试图强制使用   这个 - >成员每当一个成员   使用变量

通常是,因为没有前缀。编译器需要足够的信息来解析有问题的变量,因为前缀或this关键字是唯一的名称。

所以,是的,我认为前缀仍然有用。举个例子,我更喜欢输入'_'来访问成员而不是'this-&gt;'。

答案 12 :(得分:4)

其他语言将使用编码约定,它们往往不同。例如,C#可能有两种不同的样式,人们倾向于使用它们,或者是C ++方法之一(_variable,mVariable或其他前缀,如匈牙利表示法),或者我称之为StyleCop方法。

private int privateMember;
public int PublicMember;

public int Function(int parameter)
{
  // StyleCop enforces using this. for class members.
  this.privateMember = parameter;
}

最终,它成为人们所知道的,看起来最好的。我个人认为代码在没有匈牙利符号的情况下更具可读性,但是如果附加了匈牙利符号,则可以更容易地找到具有智能感知的变量。

在我上面的示例中,您不需要成员变量的m前缀,因为前缀与此一起使用。在编译器强制方法中表示相同的内容。

这并不一定意味着其他方法都不好,人们坚持认为有效。

答案 13 :(得分:3)

我们的项目一直使用“its”作为成员数据的前缀,“the”作为参数的前缀,没有本地化的前缀。它有点可爱,但它被我们系统的早期开发人员采用,因为他们看到它被我们当时使用的一些商业源库(XVT或RogueWave - 可能两者)用作惯例。所以你会得到这样的东西:

void
MyClass::SetName(const RWCString &theName)
{
   itsName = theName;
}

我看到的范围前缀(而不是其他人 - 我讨厌匈牙利表示法)的一个重要原因是,它可以防止你通过编写代码来防止你遇到麻烦,你认为你指的是一个变量,但你真的指的是到本地范围中定义的具有相同名称的另一个变量。它还避免了提出变量名来表示相同概念但具有不同范围的问题,如上例所示。在这种情况下,您无论如何都必须为参数“theName”提供一些前缀或不同的名称 - 为什么不制定适用于所有地方的一致规则。

只需使用此 - &gt;实际上并不是很好 - 我们对减少歧义并不像减少编码错误那样兴趣,而使用本地范围的标识符掩盖名称可能会很痛苦。当然,有些编译器可能会选择在更大范围内屏蔽名称的情况下发出警告,但如果您正在使用碰巧选择的大量第三方库,那些警告可能会变得令人讨厌偶尔与您自己碰撞的未使用变量的名称。

至于它/它本身 - 老实说,我觉得输入比下划线更容易(作为触摸打字员,我尽可能避免下划线 - 过多地拉伸主页行),我发现它比神秘的更具可读性下划线。

答案 14 :(得分:3)

C ++成员变量前缀的最初想法是存储编译器不知道的其他类型信息。因此,例如,您可以拥有一个字符串,该字符串是固定长度的字符,另一个字符串是变量并以'\ 0'结尾。对于编译器,它们都是char *,但是如果你试图从一个复制到另一个,那么你会遇到很大麻烦。所以,在我的头顶,

char *aszFred = "Hi I'm a null-terminated string";
char *arrWilma = {'O', 'o', 'p', 's'};

其中“asz”表示此变量为“ascii string(零终止)”,“arr”表示此变量是字符数组。

然后神奇的事发生了。编译器对此声明非常满意:

strcpy(arrWilma, aszFred);

但是,作为一个人,你可以看一看并说“嘿,那些变量不是真的是同一类型,我不能这样做。”

不幸的是,许多地方使用诸如“m_”之类的标准用于成员变量,“i”用于整数,无论​​使用多少,“cp”用于char指针。换句话说,它们复制了编译器所知道的内容,并使代码难以同时读取。我认为这种有害的做法应该被法规取缔,并受到严厉的惩罚。

最后,我应该提到两点:

  • 明智地使用C ++功能允许编译器知道您必须在原始C样式变量中编码的信息。您可以创建仅允许有效操作的类。这应该尽可能地完成。
  • 如果您的代码块太长,以至于在使用之前忘记了变量的类型,那么它们的方式太长了。不要使用名称,重新组织。

答案 15 :(得分:3)

正如其他人已经说过的那样,重要的是口语化(使命名样式和约定适应你正在编写的代码库)并保持一致。

多年来,我一直致力于使用“this-&gt;”的大型代码库。约定以及对成员变量使用 postfix 下划线表示法。这些年来,我还参与了较小的项目,其中一些项目没有任何命名成员变量的约定,另一些项目成员变量的命名不同。在那些较小的项目中,我一直认为那些缺乏任何惯例的项目最难以迅速进入并理解。

我对命名非常痴迷。我会痛苦地将这个名称归于一个类或变量,以至于如果我无法想出一些我觉得“好”的东西,我会选择将它命名为荒谬的东西,并提供一个描述它真实内容的评论是。这样,至少这个名字的确意味着我的意思 - 没有更多,也没有更少。通常,在使用它一段时间之后,我会发现真正的名称应该是什么,并且可以返回并适当地修改或重构。

关于IDE完成工作主题的最后一点 - 这一切都很好,但是在我执行最紧急工作的环境中,IDE通常不可用。有时,此时唯一可用的是'vi'的副本。此外,我已经看到很多情况下IDE代码完成已经传播愚蠢,例如名称拼写错误。因此,我更喜欢不必依靠IDE拐杖。

答案 16 :(得分:3)

IMO,这是个人的。我根本没有添加任何前缀。无论如何,如果代码被公开,我认为它应该更好地有一些前缀,所以它可以更具可读性。

通常大公司都在使用它自己所谓的“开发者规则” 顺便说一句,我看到的最有趣但最聪明的是DRY KISS(不要重复自己。保持简单,愚蠢)。 : - )

答案 17 :(得分:3)

当你有一个大方法或代码块时,如果你使用局部变量或成员,立即知道是很方便的。这是为了避免错误和更好的清晰度!

答案 18 :(得分:2)

我使用它是因为VC ++的Intellisense无法告诉何时在访问课外时显示私有成员。唯一的指示是Intellisense列表中字段图标上的一个小“锁定”符号。它只是更容易识别私有成员(字段)。 C#的习惯也是诚实的。

class Person {
   std::string m_Name;
public:
   std::string Name() { return m_Name; }
   void SetName(std::string name) { m_Name = name; }
};

int main() {
  Person *p = new Person();
  p->Name(); // valid
  p->m_Name; // invalid, compiler throws error. but intellisense doesn't know this..
  return 1;
}

答案 19 :(得分:2)

我认为,如果你需要前缀来区分类成员与成员函数参数和局部变量,那么函数太大或变量命名错误。如果它不适合屏幕,那么你可以很容易地看到什么是重构。

鉴于它们经常被宣布远离它们的使用位置,我发现全局常量(和全局变量,尽管IMO很少需要使用它们)的命名约定是有意义的。但除此之外,我认为没有太多需要。

那就是说,我曾经在所有私人班级成员的末尾加上一个下划线。由于我的所有数据都是私有的,这意味着成员有一个尾随下划线。我通常不会在新的代码库中执行此操作,但因为作为程序员,您主要使用旧代码,我仍然会这么做。我不确定我对这种习惯的容忍度是否来自于我以前常常这样做并且仍然经常这样做的事实,或者它是否真的比成员变量的标记更有意义。

答案 20 :(得分:2)

在python中,前导双下划线用于模拟私有成员。有关详细信息,请参阅this answer

答案 21 :(得分:1)

代码完成建议m_varname用于成员变量。

虽然我从未想过m_符号有用,但我会在制定标准时给予麦康奈尔的意见权重。

答案 22 :(得分:1)

由于内存管理,区分成员变量和局部变量很有用。从广义上讲,堆分配的成员变量应该在析构函数中销毁,而堆分配的局部变量应该在该范围内销毁。将命名约定应用于成员变量有助于正确的内存管理。

答案 23 :(得分:1)

我几乎从不在变量名前面使用前缀。如果您使用的是足够体面的IDE,您应该能够轻松地重构和查找引用。我使用非常清晰的名称,并且不怕长变量名称。我从来没有遇到过这种理念的范围问题。

我使用前缀的唯一时间是签名行。我将参数添加到带_的方法中,这样我就可以围绕它们进行防御性编程。

答案 24 :(得分:1)

你永远不需要这样的前缀。如果这样的前缀为您提供了任何优势,那么您的编码风格通常需要修复,并且它不是保持代码不清晰的前缀。典型的不良变量名称包括&#34;其他&#34;或&#34; 2&#34;。你不需要修复它,而是通过让开发人员在该函数的上下文中考虑该变量在那里做什么来解决它。也许他的意思是remoteSide,newValue或secondTestListener或者那个范围内的东西。

这是一个有效的时代错误,但仍然传播得太过分了。停止为变量添加前缀并为其指定正确的名称,这些名称的清晰度反映了它们的使用时间。最多5行你可以称之为&#34;我&#34;没有困惑;超过50行你需要一个很长的名字。

答案 25 :(得分:1)

我喜欢变量名,只对它们包含的值赋予意义,并留下如何从名称中声明/实现它们。我想知道价值意味着什么,期限。也许我做的不仅仅是平均数量的重构,但我发现在名称中嵌入如何实现某些东西会使重构变得比它需要的更乏味。指示对象成员在何处或如何声明的前缀是特定于实现的。

color = Red;

大多数时候,我不在乎Red是一个枚举,结构还是其他任何东西,如果函数太大以至于我无法记住颜色是在本地声明的还是会员,可能是时候把功能分解成更小的逻辑单元了。

如果您的圈复杂度如此之大,以至于您无法跟踪代码中发生的事情而没有嵌入在事物名称中的特定于实现的线索,那么您很可能需要降低函数的复杂性/方法

大多数情况下,我只使用&#39;这个&#39;在构造函数和初始化器中。

答案 26 :(得分:0)

我使用m_作为成员变量只是为了利用Intellisense和相关的IDE功能。当我编写类的实现时,我可以键入m_并查看组合在一起的所有m_成员的组合框。

但是,当然,我可以毫无问题地生活。这只是我的工作作风。

答案 27 :(得分:0)

根据JOINT STRIKE FIGHTER AIR VEHICLE C ++编码标准(2005年12月):

  

AV Rule 67

     

公共和受保护的数据只能用于   结构 - 不是类。理由:一个班级能够维持它   通过控制对其数据的访问来保持不变。但是,一堂课不能   如果这些成员是非私人的,则控制对其成员的访问。因此全部   一个类中的数据应该是私有的。

因此,&#34; m&#34;前缀变得无用,因为所有数据都应该是私有的。

但是在指针之前使用p前缀是一个好习惯,因为它是一个危险的变量。

答案 28 :(得分:0)

这些约定中的许多约定都是从没有高级编辑器的时代开始的。我建议您使用适当的IDE,该IDE可以为每种变量着色。到目前为止,颜色比任何前缀都更容易发现。

如果需要获取有关变量的更多详细信息,则任何现代IDE都应该能够通过将插入符号或光标移到该变量上来向您显示该变量。而且,如果您以错误的方式使用变量(例如,带有。运算符的指针),无论如何都会出现错误。