Зачем использовать префиксы для переменных-членов в классах C ++ - PullRequest
135 голосов
/ 04 августа 2009

Большая часть кода на C ++ использует синтаксические соглашения для разметки переменных-членов. Общие примеры включают

  • m_ memberName для открытых участников (где публичные участники используются вообще)
  • _ memberName для частных пользователей или всех участников

Другие пытаются принудительно использовать это -> member всякий раз, когда используется переменная-член.

По моему опыту, большинство более крупных баз кода терпят неудачу в последовательном применении таких правил.

На других языках эти соглашения гораздо менее распространены. Я вижу это только в коде Java или C #. Я думаю, что никогда не видел этого в коде Ruby или Python. Таким образом, кажется, что с более современными языками существует тенденция не использовать специальную разметку для переменных-членов.

Это соглашение все еще полезно сегодня в C ++ или это просто анахронизм. Тем более, что он так непоследовательно используется в разных библиотеках. Разве другие языки не показали, что можно обходиться без префиксов участников?

Ответы [ 28 ]

219 голосов
/ 04 августа 2009

Я все в пользу префиксов, сделанных хорошо .

Я думаю (Системная) Венгерская нотация ответственна за большую часть "плохого рэпа", который получают префиксы.

Это обозначение в значительной степени бессмысленно в строго типизированных языках, например в C ++ "lpsz", чтобы сказать вам, что ваша строка является длинным указателем на строку с нулевым символом в конце, когда: сегментированная архитектура - древняя история, строки C ++ по общему соглашению являются указателями на массив символов с нулевым символом в конце, и это не так уж сложно знать, что "customerName" - это строка!

Однако я использую префиксы для указания использования переменной (по сути, «Apps Hungarian», хотя я предпочитаю избегать термина венгерский из-за плохой и несправедливой ассоциации с System Hungarian) и это очень удобный способ экономии и уменьшения ошибок .

Я использую:

  • м для членов
  • c для констант / только для чтения
  • p для указателя (и pp для указателя на указатель)
  • V для летучих
  • с для статического
  • i для индексов и итераторов
  • e для событий

Там, где я хочу сделать тип понятным, я использую стандартные суффиксы (например, List, ComboBox и т. Д.).

Это позволяет программисту знать о использовании переменной всякий раз, когда он видит / использует ее. Возможно, наиболее важным случаем является «p» для указателя (потому что использование меняется с var. На var-> и вам нужно быть намного осторожнее с указателями - NULL, арифметикой указателей и т. Д.), Но все остальные очень удобны.

Например, вы можете использовать одно и то же имя переменной несколькими способами в одной функции: (здесь пример C ++, но он в равной степени применим ко многим языкам)

MyClass::MyClass(int numItems)
{
    mNumItems = numItems;
    for (int iItem = 0; iItem < mNumItems; iItem++)
    {
        Item *pItem = new Item();
        itemList[iItem] = pItem;
    }
}

Вы можете увидеть здесь:

  • Нет путаницы между элементом и параметром
  • Нет путаницы между индексом / итератором и элементами
  • Использование набора четко связанных переменных (список элементов, указатель и индекс), которые позволяют избежать многих ловушек общих (расплывчатых) имен, таких как "count", "index".
  • Префиксы уменьшают набор текста (короче, и работают лучше с автозаполнением), чем альтернативы, такие как "itemIndex" и "itemPtr"

Еще одним замечательным моментом итераторов iName является то, что я никогда не индексирую массив с неверным индексом, и если я копирую цикл внутри другого цикла, мне не нужно реорганизовывать одну из переменных индекса цикла.

Сравните этот нереально простой пример:

for (int i = 0; i < 100; i++)
    for (int j = 0; j < 5; j++)
        list[i].score += other[j].score;

(который трудно читать и часто приводит к использованию «i» там, где «j» предназначался)

с:

for (int iCompany = 0; iCompany < numCompanies; iCompany++)
    for (int iUser = 0; iUser < numUsers; iUser++)
       companyList[iCompany].score += userList[iUser].score;

(что намного удобочитаемее и устраняет путаницу при индексировании. С автозаполнением в современных IDE это также быстро и легко вводится)

Следующим преимуществом является то, что фрагменты кода не требуют понимания контекста . Я могу скопировать две строки кода в электронное письмо или документ, и любой, кто читает этот фрагмент, может заметить разницу между всеми членами, константами, указателями, индексами и т. Д. Мне не нужно добавлять «о, и будьте осторожны, потому что «data» - это указатель на указатель », потому что он называется« ppData ».

И по той же причине мне не нужно отводить взгляд от строки кода, чтобы понять это. Мне не нужно искать в коде, чтобы определить, является ли «data» локальным, параметром, членом или константой. Мне не нужно подносить руку к мыши, чтобы я мог навести указатель мыши на «данные», а затем дождаться всплывающей подсказки (которая иногда не появляется). Таким образом, программисты могут читать и понимать код значительно быстрее, потому что они не тратят время на поиск вверх и вниз или ожидание.

(Если вы не думаете, что тратите время на поиски и поиски вещей, найдите код, который вы написали год назад и на который не смотрели поскольку. Откройте файл и прыгайте на полпути вниз, не читая его. Посмотрите, как далеко вы можете читать с этого момента, прежде чем вы не знаете, есличто-то является членом, параметром или локальным. Теперь перейдите к другому случайному местоположение ... Это то, что мы все делаем целый день, когда мы одиноки перебирая чужой код или пытаясь понять, как вызовите их функцию)

Префикс 'm' также позволяет избежать (ИМХО) уродливой и многословной нотации "this->" и несоответствия, которое он гарантирует (даже если вы будете осторожны, у вас, как правило, будет смесь 'this-> data 'и' data 'в одном классе, потому что ничто не обеспечивает согласованное написание имени).

эта «нотация» предназначена для разрешения неоднозначности - но зачем кому-то сознательно писать код, который может быть неоднозначным? Неоднозначность рано или поздно приведет к ошибке 1089. А в некоторых языках «this» нельзя использовать для статических членов, поэтому вам нужно ввести «особые случаи» в свой стиль кодирования. Я предпочитаю иметь одно простое правило кодирования, которое применяется везде - явное, однозначное и последовательное.

Последнее главное преимущество - Intellisense и автозаполнение. Попробуйте использовать Intellisense в форме Windows для поиска события - вам нужно прокрутить сотни таинственных методов базового класса, которые вам никогда не понадобятся для поиска событий. Но если бы у каждого события был префикс «e», они были бы автоматически перечислены в группе под «e». Таким образом, префикс работает для группировки членов, констант, событий и т. Д. В списке intellisense, что позволяет гораздо быстрее и проще находить нужные имена. (Обычно метод может иметь около 20-50 значений (локальные, параметры, члены, константы, события), которые доступны в его области. Но после ввода префикса (я хочу использовать индекс сейчас, поэтому я набираю 'i. .. '), у меня есть только 2-5 опций автозаполнения. Атрибуты "дополнительная типизация", приписываемые префиксам и осмысленным именам, значительно сокращают пространство поиска и заметно увеличивают скорость разработки)

Я ленивый программист, и вышеприведенное соглашение спасает мне много работы. Я могу писать быстрее и совершать гораздо меньше ошибок, потому что знаю, как использовать каждую переменную.


Аргументы против

Итак, каковы минусы? Типичные аргументы против префиксов:

  • «Префиксные схемы плохие / злые» . Я согласен с тем, что «m_lpsz» и тому подобное плохо продуманы и совершенно бесполезны. Вот почему я бы посоветовал использовать хорошо разработанную нотацию, разработанную для поддержки ваших требований, а не копировать что-то, что не подходит для вашего контекста. (Используйте правильный инструмент для работы).

  • «Если я изменяю использование чего-либо, я должен переименовать его» * ​​1112 *. Да, конечно, вы понимаете, вот что такое рефакторинг, и почему в IDE есть инструменты рефакторинга, чтобы выполнять эту работу быстро и безболезненно. Даже без префиксов изменение использования переменной почти наверняка означает, что ее имя должно быть изменено.

  • «Префиксы меня просто смущают» . Как и каждый инструмент, пока вы не научитесь его использовать. Как только ваш мозг привыкнет к шаблонам именования, он автоматически отфильтрует информацию, и вы не будете возражать, что префиксы уже есть. Но вы должны использовать такую ​​схему в течение недели или двух, прежде чем вы действительно станете "свободно". И вот тогда многие люди смотрят на старый код и начинают задумываться, как им удавалось без хорошей схемы префиксов.

  • «Я могу просто посмотреть на код, чтобы разобраться с этим» . Да, но вам не нужно тратить время на поиск других частей кода или запоминание каждой мелочи, когда ответ прямо на месте, на котором уже сфокусирован ваш глаз.

  • (некоторая часть) эту информацию можно найти, просто подождав всплывающей подсказки для моей переменной . Да. Там, где это поддерживается, для некоторых типов префиксов, когда ваш код правильно компилируется, после ожидания вы можете прочитать описание и найти информацию, которую префикс передал бы мгновенно. Я чувствую, что префикс - это более простой, надежный и эффективный подход.

  • "Это больше печатать" . В самом деле? Еще один целый персонаж? Или это так - с помощью инструментов автозаполнения IDE это часто сокращает ввод текста, поскольку каждый префиксный символ значительно сужает пространство поиска. Нажмите «e», и три события в вашем классе всплывают в intellisense. Нажмите «c», и пять констант будут перечислены.

  • «Я могу использовать this-> вместо m» . Ну, да, вы можете. Но это просто более уродливый и более подробный префикс! Только он несет гораздо больший риск (особенно в командах), потому что для компилятора это необязательно , и поэтому его использование часто противоречиво. m, с другой стороны, является кратким, ясным, явным и необязательным, поэтому намного труднее делать ошибки при его использовании.

106 голосов
/ 04 августа 2009

Я обычно не использую префикс для переменных-членов.

Я использовал префикс m, пока кто-то не указал, что «C ++ уже имеет стандартный префикс для доступа к члену: this->.

Так вот, что я сейчас использую. То есть, когда есть неоднозначность , я добавляю префикс this->, но обычно не существует двусмысленности, и я могу просто обратиться непосредственно к имени переменной.

Для меня это лучшее из обоих миров. У меня есть префикс, который я могу использовать, когда мне это нужно, и я могу по возможности его опускать.

Конечно, очевидным счетчиком этого является «да, но тогда вы не сможете сразу увидеть, является ли переменная членом класса или нет».

На что я говорю «ну и что? Если вам нужно это знать, ваш класс, вероятно, имеет слишком много состояний. Или функция слишком большая и сложная».

На практике я обнаружил, что это работает очень хорошо. В качестве дополнительного бонуса он позволяет мне легко выдвигать локальную переменную для члена класса (или наоборот), не переименовывая ее.

И, что лучше всего, это соответствует! Мне не нужно делать ничего особенного или помнить какие-либо соглашения, чтобы поддерживать последовательность.


Кстати, вы не должны использовать начальные подчеркивания для ваших учеников. Вы неудобно приближаетесь к именам, которые зарезервированы реализацией.

Стандарт резервирует все имена, начинающиеся с двойного или нижнего подчеркивания, за которым следует заглавная буква. Он также резервирует все имена, начинающиеся с одного подчеркивания в глобальном пространстве имен .

Таким образом, ученик с начальным подчеркиванием, за которым следует строчная буква, является законным, но рано или поздно вы собираетесь сделать то же самое с идентификатором, начинающимся с прописной буквы, или иным образом нарушить одно из вышеуказанных правил .

Так что проще просто избегать подчеркивания. Используйте подчеркивание постфикса или префикс m_ или просто m, если вы хотите закодировать область в имени переменной.

44 голосов
/ 04 августа 2009

Вы должны быть осторожны с использованием нижнего подчеркивания. Начальное подчеркивание перед прописной буквой в слове зарезервировано. Например:

_foo

_L

все зарезервированные слова, в то время как

_foo

_l

нет. Есть и другие ситуации, когда начальные подчеркивания перед строчными буквами не допускаются. В моем конкретном случае я обнаружил, что _L зарезервирован Visual C ++ 2005, и столкновение привело к неожиданным результатам.

Я стою на пороге того, насколько полезно маркировать локальные переменные.

Вот ссылка, по которой зарезервированы идентификаторы: Каковы правила использования подчеркивания в идентификаторе C ++?

33 голосов
/ 04 августа 2009

Я предпочитаю подчеркивания postfix, например:

class Foo
{
   private:
      int bar_;

   public:
      int bar() { return bar_; }
};
19 голосов
/ 29 августа 2009

В последнее время я склоняюсь к тому, чтобы предпочесть префикс 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: Причина, по которой вы не видите m_ в Python или Ruby, заключается в том, что оба языка применяют свой собственный префикс, Ruby использует @ для переменных-членов, а Python требует self..

10 голосов
/ 21 октября 2011

При чтении через функцию-член, знание того, кто «владеет» каждой переменной, абсолютно необходимо для понимания значения переменной. В такой функции:

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->» вместо m_ имеет смысл, но является еще более многословным и визуально разрушительным. Я не считаю его хорошей альтернативой для разметки всех вариантов использования переменных-членов.) *

Возможное возражение против приведенного выше аргумента - расширить аргумент на типы. Также может быть верно, что знание типа переменной «абсолютно необходимо для понимания значения переменной». Если это так, почему бы не добавить префикс к каждому имени переменной, который идентифицирует ее тип? С этой логикой вы получите венгерскую нотацию. Но многие люди находят венгерские обозначения трудоемкими, безобразными и бесполезными.

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_ превосходят затраты в глазах многих программистов.

10 голосов
/ 04 августа 2009

Я не могу сказать, насколько она широка, но, говоря лично, я всегда (и всегда) начинал с префиксов моих переменных-членов с 'm'. E.g.:

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

Это единственная форма префикса, которую я использую (я очень против венгерской нотации), но она зарекомендовала себя на протяжении многих лет. Кроме того, я обычно ненавижу использование подчеркивания в именах (или где-либо еще в этом отношении), но делаю исключение для имен макросов препроцессора, так как они обычно все в верхнем регистре.

7 голосов
/ 05 августа 2009

Основная причина префикса члена состоит в том, чтобы различать локальную функцию-член и переменную-член с тем же именем. Это полезно, если вы используете геттеры с названием вещи.

Рассмотрим:

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 () или как-то украсить переменную-член.

6 голосов
/ 04 августа 2009

Я не думаю, что один синтаксис имеет реальное значение над другим. Как все вы упомянули, все сводится к однородности исходных файлов.

Единственный момент, когда я нахожу такие правила интересными, - это когда мне нужны 2 вещи с одинаковым именем, например:

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

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

Я использую это, чтобы различать два. Также, когда я обертываю вызовы, как из Windows Dll, RecvPacket (...) из Dll может быть заключен в RecvPacket (...) в моем коде. В этих конкретных случаях использование префикса, такого как _, может привести к тому, что эти два будут выглядеть одинаково, легко определить, какой есть, но отличается для компилятора

6 голосов
/ 04 августа 2009

В некоторых ответах основное внимание уделяется рефакторингу, а не соглашениям о присвоении имен, как способу улучшения читаемости. Я не чувствую, что одно может заменить другое.

Я знал программистов, которым неудобно использовать локальные объявления; они предпочитают размещать все объявления в верхней части блока (как в C), чтобы они знали, где их найти. Я обнаружил, что там, где это позволяет область видимости, объявление переменных, где они впервые используются, уменьшает время, которое я трачу, оглядываясь назад, чтобы найти объявления. (Это верно для меня даже для небольших функций.) Это облегчает мне понимание кода, на который я смотрю.

Надеюсь, достаточно ясно, как это относится к соглашениям об именах членов: когда члены имеют одинаковый префикс, мне никогда не придется оглядываться назад; Я знаю, что объявление даже не будет найдено в исходном файле.

Я уверен, что я не начал отдавать предпочтение этим стилям. Однако со временем, работая в среде, где они использовались последовательно, я оптимизировал свое мышление, чтобы использовать их в своих интересах. Я думаю, что возможно, что многие люди, которые в настоящее время чувствуют себя некомфортно с ними, также предпочтут их, учитывая постоянное использование.

...