Зачем использовать префиксы для переменных-членов в классах C ++


151

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

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

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

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

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

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


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

5
Вы никогда не видели его в Ruby из-за атрибута @ for и особенностей генерации методов доступа, предпочитающих использовать атрибуты напрямую.
Стив Джессоп

6
Согласно PEP 8 непубличные переменные-члены должны иметь префикс с подчеркиванием в Python (пример:) self._something = 1.
Натан Осман

2
Разве редакторская подсветка не должна использоваться для их идентификации?
тоже

2
Вы видели эквивалент this->memberв коде Python. В Python это обычно так, self.memberи это не только соглашение, это требуется языком.
matec

Ответы:


49

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

_foo

_L

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

_foo

_l

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

Я нахожусь на заборе о том, как полезно разметить локальные переменные.

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


4
На самом деле, и _foo, и _l зарезервированы в области имен.

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

11
Это не зарезервированные слова. Это зарезервированные имена. Если бы они были зарезервированными словами, вы не могли бы использовать их вообще. Поскольку они являются зарезервированными именами, вы можете использовать их, но на свой страх и риск.
TonyK

236

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

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

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

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

Я использую:

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

Там, где я хочу прояснить тип , я использую стандартные суффиксы (например, 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» также позволяет избежать (IMHO) уродливой и многословной нотации «this->» и несогласованности, которую он гарантирует (даже если вы будете осторожны, вы, как правило, в конечном итоге получите смесь «this-> data» и 'data' в том же классе, потому что ничто не обеспечивает согласованное написание имени).

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

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

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


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

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

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

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

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

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

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

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

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


6
Я имею в виду, что прочитал, что проблема с нотацией Hungarien просто возникла из-за неправильного понимания Симони. Он написал префикс, который должен использоваться для указания типа переменной, где он имел в виду «тип», как в «типе вещей», а не в буквальном типе данных. Позже ребята из платформы Microsoft подхватили его и предложили lpsz ... а остальное уже история ...
VoidPointer

19
«s is for static» звучит во многом как «плохая» форма венгерского языка для меня.
Джалф

6
@Mehrdad: Я не думаю, что zэто очень часто полезно в таких языках, как C ++, где такие подробности реализации низкого уровня должны быть инкапсулированы в классе, но в C (где нулевое завершение является важным отличием) я согласен с вами. IMO любая схема, которую мы используем, должна быть адаптирована так, как требуется, чтобы лучше всего соответствовать нашим собственным потребностям - поэтому, если нулевое завершение влияет на использование вами переменной, нет ничего плохого в объявлении «z» полезным префиксом.
Джейсон Уильямс

14
The most important case is "p" for pointer (because the usage changes from var. to var-> and you have to be much more careful with pointers.Я от всего сердца не согласен. Если я неправильно использую указатель, он просто не скомпилируется ( void*хотя может быть исключением для двойных указателей). И все ->более .достаточно , чтобы сказать мне это указатель. Кроме того, если вы используете автозаполнение, ваш редактор, вероятно, имеет всплывающие подсказки для объявления, что устраняет необходимость в префиксе для переменной информации. В любом случае, хороший ответ.
Томас Эдинг

5
Голосовали за четкие, исчерпывающие и интересные объяснения, однако здесь мало что говорит о том, как это экономит время в C ++. Но во многих других языках по большей части не используется.

115

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

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

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

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

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

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

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

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


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

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

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

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


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

3
@mbarnett: Нет, подчеркивание, за которым следует верхний регистр, зарезервировано в целом , а не только в глобальном пространстве имен.
Джалф

9
Удивлен, что голос за этот ответ меньше, чем префикс.
Марсон Мао

Я согласен с этим ответом, просто используйте, this->если вам нужно указать, что это переменная-член, или нет, это тоже хорошо.
Дэвид Мортон

Более того, вам не нужно документировать свое соглашение, чтобы передать свой код другим людям. Все понимают, что this->значит.
Кадушон

34

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

class Foo
{
   private:
      int bar_;

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

Я тоже. Я также даю аксессорам / мутаторам то же имя.
Роб

4
Интересный. Поначалу выглядит немного некрасиво, но я понимаю, как это может быть полезно.
Я23

6
Я бы сказал, что это намного менее уродливо, чем: "mBar" или "m_bar".
Сидан

6
но тогда у вас есть vector<int> v_;и писать v_.push_back(5)тоже довольно некрасиво
авим

4
Это стиль Google C ++.
Justme0

20

В последнее время я склоняюсь к тому, чтобы предпочесть префикс m_ вместо того, чтобы вообще не иметь префикса, причина не столько в том, что важно помечать переменные-члены, но в том, что он избегает неоднозначности, скажем, у вас есть такой код:

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

Причина не работает, fooразрешено только одно . Итак, ваши варианты:

  • this->foo = foo;

    Мне это не нравится, так как это вызывает затенение параметров, вы больше не можете использовать g++ -Wshadowпредупреждения, его также дольше набирать m_. Вы также все еще сталкиваетесь с конфликтами имен между переменными и функциями, когда у вас есть a int foo;и a 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..


1
чтобы быть справедливым, вы пропустили как минимум 2 других варианта, например (а) использовать полные имена, такие как fooтолько для членов, и вместо этого использовать однобуквенные или короткие имена для параметров или других локальных / одноразовых номеров, таких как int f; или (б) префикс параметров или других локальных объектов с чем-то. хорошая точка зрения m_и стручки, хотя; я независимо пришел к предпочтению следовать обеим этим рекомендациям, по большей части.
underscore_d

12

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

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"), велико. Так,


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

10

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

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

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


5
Проблема с использованием m, а не m_ (или _) заключается в том, что в текущем случае верблюдов трудно читать имена некоторых переменных.
Мартин Беккет

1
@ Нейл, я с тобой. @mgb: я ненавижу имена, начинающиеся с '_'. Это просто приглашение к тому, что что-то пойдет не так в будущем.
Мартин Йорк,

1
@Neil: Какое соглашение вы используете тогда, если вы не используете подчеркивания и не используете camelcase?
Джалф

2
Насколько я понимаю, именно camelCase сбивает с толку использование только m для таких переменных, как «apData» - оно становится «mapData», а не «m_apData». Я использую _camelCase для защищенных / закрытых переменных-членов, потому что они выделяются
Мартин Беккет,

10
@MartinBeckett: Вы должны использовать заглавные буквы aв этом сценарии - иначе вы делаете это неправильно. mApData( mпрефикс, затем имя переменной apData).
Платиновая Лазурь

8

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

Рассматривать:

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


1
Это причина, по которой я префикс. Я думаю, что foo.name()гораздо более читабельным, чем foo.get_name()на мой взгляд.
Terrabits

6

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

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

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

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

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


6

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

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

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

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


5

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


«между такими вещами, как публичные и частные члены» - насколько это на самом деле общее? я не помню, чтобы видел это, но опять же, я не хожу по обзору кодовых баз или чего-то еще.
underscore_d

Я не делаю это в своем собственном кодировании, но я работал в тех местах, где нам приходилось делать это на основе их руководств по кодированию. Я предпочитаю этого не делать, так как почти все IDE будут показывать частные переменные другого цвета.
Мистер Уилл

Хм, я думаю, это происходит только в других ситуациях, чем у меня. Обычно я использую либо classвсе члены которого являются private/ protected, либо POD structвсе чьи переменные public(и часто также const). Таким образом, мне никогда не нужно задумываться об уровне доступа любого данного члена.
underscore_d

5

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

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

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


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

1
Именно. Местные жители будут скрывать учеников. Рассмотрим конструктор, который устанавливает эти члены. Обычно имеет смысл называть параметры такими же, как и элементы.
Кент Boogaart

6
Почему это запах кода? Я бы сказал, что это совершенно распространено и разумно, особенно когда дело доходит до конструкторов.
Кент Boogaart

3
Конструктор должен (обычно) устанавливать локальные объекты в своем списке инициализации. И там параметры не скрывают имена полей, но оба доступны - так что вы можете писатьstruct Foo { int x; Foo(int x) : x(x) { ... } };
Павел Минаев

2
Я предполагаю, что проблема с этим возникает, когда вы Foo(int x, bool blee) : x(x) { if (blee) x += bleecount; } // oops, forgot this->предпочитаете называть мои переменные-члены чем-то полезным, а затем задаете параметры конструктора, которые соответствуют их сокращенным именам:Foo(int f) : foo(f) {...}
Стив Джессоп,

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

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

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

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


3

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


3
Если у вас есть большой метод, для большей ясности разбейте его.
ВОО

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

3
@sbi: Руководящие принципы - только это; руководящие принципы, а не правила. Иногда вам нужны большие методы, которые логически не поддаются разделению, а иногда имена параметров конфликтуют с членами.
Эд С.

Пожалуйста, не делайте ваши переменные-члены публичными. Просто используйте аксессоры. Скобки должны указывать читателю, что это переменная-член.
jkeys

Обратите внимание, что в gcc есть предупреждение (> = 4.6) для обнаружения конфликта имен:-Wshadow
Caduchon

3

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

Часто крупные компании используют свои собственные так называемые «правила разработчика».
Кстати, самым забавным, но самым умным из тех, что я видел, был СУХОЙ ПОЦЕЛУЙ (не повторяйся. Оставайся простым, глупый) :-)


3

Как уже говорили другие, важно быть разговорным (адаптировать стили именования и соглашения к базе кода, в которой вы пишете) и быть последовательным.

В течение многих лет я работал над большой кодовой базой, которая использует как соглашение "this->", так и использование постфикса нотация с подчеркиванием в для переменных-членов. На протяжении многих лет я также работал над небольшими проектами, некоторые из которых не имели каких-либо соглашений для именования переменных-членов, а другие имели разные соглашения для именования переменных-членов. Из этих небольших проектов я всегда находил те, в которых отсутствовало какое-либо соглашение, наиболее сложным и быстрым для понимания.

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

И последнее замечание по поводу того, как IDE выполняет свою работу - это все хорошо и хорошо, но IDE часто недоступны в тех средах, где я выполняю самую неотложную работу. Иногда единственной вещью, доступной на тот момент, является копия «vi». Кроме того, я видел много случаев, когда завершение кода IDE распространяло глупость, такую ​​как неправильное написание в именах. Таким образом, я предпочитаю не полагаться на костыль IDE.


3

Первоначальная идея префиксов в переменных-членах C ++ заключалась в том, чтобы хранить дополнительную информацию о типах, о которой компилятор не знал. Так, например, у вас может быть строка фиксированной длины символов, а другая переменная, оканчивающаяся на «\ 0». Для компилятора они оба char *, но если вы попытаетесь скопировать данные с одного на другое, у вас будут большие проблемы. Итак, с моей головы,

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

где «asz» означает, что эта переменная является «строкой ascii (оканчивающейся нулем), а« arr »означает, что эта переменная является массивом символов.

Тогда волшебство случается. Компилятор будет очень доволен этим утверждением:

strcpy(arrWilma, aszFred);

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

К сожалению, во многих местах используются такие стандарты, как «m_» для переменных-членов, «i» для целых чисел, независимо от того, как они используются, «cp» для указателей на символы. Другими словами, они дублируют то, что знает компилятор, и в то же время затрудняют чтение кода. Я считаю, что эта пагубная практика должна быть запрещена законом и подлежать суровым наказаниям.

Наконец, я должен упомянуть два момента:

  • Разумное использование функций C ++ позволяет компилятору знать информацию, которую вы должны были закодировать в необработанных переменных в стиле C. Вы можете создавать классы, которые будут разрешать только допустимые операции. Это должно быть сделано как можно более практичным.
  • Если кодовые блоки настолько долго , что вы забыли , что тип переменной , прежде чем использовать его, они путь слишком долго. Не используйте имена, реорганизуйте.

Префиксы, которые указывают тип или тип переменной, также заслуживают обсуждения, но я имел в виду в основном префиксы, указывающие, является ли что-то (частным) элементом / полем. Обратная венгерская нотация, которую вы упоминаете, может быть очень полезной при разумном применении (как в вашем примере). Мой любимый пример, где это имеет смысл, это относительные и абсолютные координаты. когда вы видите absX = relX, вы можете ясно видеть, что что-то может быть не так. Вы также можете назвать функции соответствующим образом: absX = absFromRel (relX, offset);
VoidPointer

Примечание: инициализация aszFred сомнительна (предлагает неконстантный доступ к литеральной строке), а инициализация arrWilma даже не будет компилироваться. (Вы, вероятно, намеревались объявить arrWilma как массив, а не как указатель!) Без проблем, так как вы написали, что это просто у вас в голове ... :-)
Niels Dekker

Ой, ты абсолютно прав. Дети, не пытайтесь делать это дома. Сделайте это: 'const char * aszFred = "Привет, я заканчиваю нулем"; char arrWilma [] = {'O', 'o', 'p', 's'}; '
А.Л. Фланаган

3

Наш проект всегда использовал «свое» в качестве префикса для данных членов и «» в качестве префикса для параметров, без префикса для локальных пользователей. Это немного неприятно, но оно было принято ранними разработчиками нашей системы, потому что они видели, как она использовалась в качестве соглашения некоторыми коммерческими исходными библиотеками, которые мы использовали в то время (либо XVT, либо RogueWave - возможно, оба). Таким образом, вы получите что-то вроде этого:

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

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

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

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


Это наиболее интуитивное решение с самой быстрой кривой обучения, которую я когда-либо слышал. Мне бы хотелось, чтобы разговорные языки были более гибкими, чтобы справляться со всеми этими проблемами, чтобы нам не приходилось думать о разработке новых методов для устранения неоднозначностей в коде.
Гуней Озсан

2

Я использую его, потому что IntelliSense VC ++ не может сказать, когда показывать закрытые члены при доступе из класса. Единственным указанием является небольшой символ «блокировки» на значке поля в списке 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;
}

2

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

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

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


2
Это очень сильно отражает мои чувства по этому вопросу. Код должен быть читаемым, не прибегая к префиксам. Возможно, мы не видим такого большого количества префиксов, используемых в более современных языках, потому что их пользовательские сообщества воспринимают читабельность чуть больше, чем то, что вы иногда видите в C ++. Конечно, C ++ может и должен быть читабельным. Просто за эти годы было написано много нечитаемого С ++.
VoidPointer


1

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


как так? Деструктор не имеет доступа к локальным переменным, объявленным в других функциях, поэтому здесь нет места для путаницы. Кроме того, выделенные в куче локальные переменные не должны существовать . И переменные-члены, выделенные в куче, должны существовать только внутри классов RAII.
Джалф

«Локальные переменные, выделенные в куче, не должны существовать» - это немного сильно. Но если / когда вы используете их, очень важно убедиться, что они освобождаются централизованно, поэтому дисциплинированное соглашение об именах для членов по сравнению с локальными переменными неизмеримо помогает в этом.
frankster

1

Code Complete рекомендует m_varname для переменных-членов.

Хотя я никогда не думал, что нотация m_ полезна, я бы учел мнение Макконнелла при разработке стандарта.


2
Нет, если он не объясняет, почему подчеркивание. Я большой поклонник его книги «Быстрое развитие», которую я рекомендовал здесь много раз, но гораздо меньше «Код завершен» (которую, я признаю, я не читал с тех пор, как она вышла в свет).

1

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

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


1

Вам никогда не понадобится такой префикс. Если такой префикс дает вам какое-либо преимущество, ваш стиль кодирования в целом нуждается в исправлении, и это не префикс, который удерживает ваш код от ясности. Типичные неверные имена переменных включают в себя «other» или «2». Вы не исправляете это, требуя, чтобы это было mOther, вы исправляете это, заставляя разработчика думать о том, что эта переменная делает там в контексте этой функции. Возможно, он имел в виду remoteSide, newValue, secondTestListener или что-то в этом роде.

Это эффективный анахронизм, который все еще распространяется слишком далеко. Прекратите префикс ваших переменных и дайте им имена, чья ясность отражает, как долго они используются. До 5 строк вы можете назвать это «я» без путаницы; за 50 строк нужно довольно длинное имя.


1

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

color = Red;

В большинстве случаев мне все равно, является ли Red перечислением, структурой или чем-то еще, и если функция настолько велика, что я не могу вспомнить, был ли цвет объявлен локально или является членом, вероятно, пришло время прервать функция в меньшие логические единицы.

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

В основном я использую «это» только в конструкторах и инициализаторах.


0

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

Но я мог бы без проблем жить без проблем, конечно. Это просто мой стиль работы.


Вы также можете напечататьthis->
Тост

0

Согласно СТАНДАРТАМ КОДИРОВАНИЯ СОВМЕСТНОГО СТРАХОВОГО ВОЗДУШНОГО АВТОМОБИЛЯ C ++ (декабрь 2005 года):

AV Правило 67

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

Таким образом, префикс «m» становится бесполезным, поскольку все данные должны быть конфиденциальными.

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


0

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

Если вам необходимо получить более подробную информацию о переменной, любая современная среда IDE сможет показать ее вам, переместив курсор или курсор на нее. И если вы используете переменную неправильно (например, указатель с оператором.), Вы все равно получите ошибку.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.