Какие ваши любимые идиомы стиля программирования C++? Я спрашиваю о стиле или типографике кодирования, например о том, где вы ставите фигурные скобки, есть ли пробелы после ключевых слов, размер отступов и т.д.
Вот пример одного из моих любимых: в инициализаторах классов C++ мы ставим разделители в начале строки, а не в конце. Это упрощает обновление данных. Это также означает, что различия в управлении исходным кодом между версиями более четкие.
TextFileProcessor::
TextFileProcessor( class ConstStringFinder& theConstStringFinder )
: TextFileProcessor_Base( theConstStringFinder )
, m_ThreadHandle ( NULL )
, m_startNLSearch ( 0 )
, m_endNLSearch ( 0 )
, m_LineEndGetIdx ( 0 )
, m_LineEndPutIdx ( 0 )
, m_LineEnds ( new const void*[ sc_LineEndSize ] )
{
;
}
Это практически дубликат stackoverflow.com/questions/66268/… или stackoverflow.com/questions/145570/….
Здесь вы говорите о стиле программирования, а не о идиомах.





Напишите каждый аргумент метода или функции в отдельной строке, чтобы их можно было легко комментировать.
int ReturnMaxValue(
int* inputList, /* the list of integer values from which to get the maximum */
long size, /* count of the number of integer values in inputList */
char* extraArgs /* additional arguments that a caller can provide. */
)
Используйте осмысленные имена переменных, а не комментарии: int ReturnMaxValue (int * inputList, long inputListSize). Комментарий к extraArgs сказал мне примерно столько же, сколько и имя переменной - ничего (да, я знаю, что это всего лишь пример, но он не очень хороший).
Полностью согласен с Андреасом. Если вам действительно нужно документировать свои параметры, используйте формат Doxygen.
Задокументируйте возвращаемые значения в строке функции, чтобы их было очень легко найти.
int function(void) /* return 1 on success, 0 on failure */
{
return 1;
};
Установка лишней точки с запятой в конце блока, в котором точка с запятой не требуется, - одна из моих наименее любимых идиом стиля кодирования C++.
Это предложение стиля не помогает ни в чем, кроме простейших возвращаемых значений.
Ерунда. Вы можете сделать комментарий настолько большим или маленьким, насколько это необходимо. Когда возникает вопрос: «Что это возвращает?» куда ты идешь? руководство пользователя? hpp файл? cpp файл? Если вы посмотрите на функцию, она тут же. Большинство IDE могут выбрать комментарий и показать его вам в контексте.
Тщательное чтение кода в попытках выяснить, что возвращает функция, - пустая трата времени разработчика. Сделайте всем одолжение, прокомментируйте возвращаемые значения в месте, которое легко найти.
Разве в коде не нужна пара скобок () после function?
@evil звучит так, будто можно поставить комментарий на одну строку перед функциональной строкой в вашем комментарии. но в своем ответе вы говорите, что эта идиома включает размещение комментария точно в строке функции. следовательно, у вас будет только одна строка для документирования сложных вычислений возвращаемого значения.
Да, для простых вещей это работает. Также есть сильное усилие, чтобы поместить его перед функцией, чтобы msvc мог его увидеть и уловить.
Не уверен, что это считается идиомой, но я обычно использую встроенные комментарии в стиле доксиген, даже если проект еще не использует doxygen ...
bool MyObjects::isUpToSomething() ///< Is my object up to something
(в сторону. Мои комментарии обычно не такие уж неубедительные.)
В операторах if при сложных условиях вы можете четко показать, на каком уровне каждое условие использует отступ.
if ( ( (var1A == var2A)
|| (var1B == var2B))
&& ( (var1C == var2C)
|| (var1D == var2D)))
{
// do something
}
Орфографические ошибки и опечатки также исправляются в коде, над которым я работаю!
Если бы это был мой код, я бы поставил два || условия в той же строке, но разделите условия &&, как вы это сделали, в результате получится двухстрочный оператор if.
Мне нравится выстраивать код / инициализации в «столбцы» ... Оказывается очень полезным при редактировании с помощью редактора, поддерживающего режим «столбец», а также мне кажется, что его намного легче читать ...
int myVar = 1; // comment 1
int myLongerVar = 200; // comment 2
MyStruct arrayOfMyStruct[] =
{
// Name, timeout, valid
{"A string", 1000, true }, // Comment 1
{"Another string", 2000, false }, // Comment 2
{"Yet another string", 11111000, false }, // Comment 3
{NULL, 5, true }, // Comment 4
};
Напротив, появится тот же код без отступа и форматирования, как указано выше ... (немного сложнее читать на мой взгляд)
int myVar = 1; // comment 1
int myLongerVar = 200; // comment 2
MyStruct arrayOfMyStruct[] =
{
// Name, timeout, valid
{"A string", 1000, true},// Comment 1
{"Another string", 2000, false }, // Comment 2
{"Yet another string", 11111000,false}, // Comment 3
{NULL, 5, true }, // Comment 4
};
Эта проблема заключается в том, что когда одна строка становится слишком длинной (скажем, вам нужно добавить «Еще одна строка, но эта длиннее остальных»), вам нужно либо нарушить хороший шаблон, либо переформатировать все, начав к кошмару системы управления версиями, где каждая строка меняется.
Возможно, вы захотите взглянуть на плагин Align для Vim.
Нет избранного, но я исправляю код воля, который имеет:
Вот однострочный способ поиска проблемных файлов:
git grep -I -E '<tab>|.{81,}| *$' | cut -f1 -d: | sort -u
где <tab> - символ табуляции (регулярное выражение POSIX не выполняет \ t)
Хорошо, что символы TAB должны использоваться для отступов, а не форматирования. Для форматирования следует использовать пробелы. Таким образом, код будет хорошо отображаться при любой настройке ширины TAB. И в любом случае табуляция должна состоять из 8 символов.
Хех ... Конечные пробелы так раздражают! Мне приходится прикусывать язык всякий раз, когда я наблюдаю, как кто-то кодирует, и они оставляют пробелы в конце строки (скажем, при разрыве строки, помещая курсор после пробела и нажимая Enter).
Проблема с конечным пробелом в том, что вы его не видите.
При создании перечислений поместите их в пространство имен, чтобы вы могли получить к ним доступ с осмысленным именем:
namespace EntityType {
enum Enum {
Ground = 0,
Human,
Aerial,
Total
};
}
void foo(EntityType::Enum entityType)
{
if (entityType == EntityType::Ground) {
/*code*/
}
}
РЕДАКТИРОВАТЬ: Однако этот метод устарел в C++ 11. Вместо этого следует использовать Перечисление с ограничением (объявленный с enum class или enum struct): он более безопасен по типу, краток и гибок. В перечислениях в старом стиле значения помещаются во внешнюю область видимости. При нумерации в новом стиле они помещаются в область действия имени enum class.
Предыдущий пример переписан с использованием перечисления с ограниченной областью видимости (также известного как строго типизированное перечисление):
enum class EntityType {
Ground = 0,
Human,
Aerial,
Total
};
void foo(EntityType entityType)
{
if (entityType == EntityType::Ground) {
/*code*/
}
}
Есть и другие существенные преимущества от использования перечислений с ограниченными областями: отсутствие неявного приведения типов, возможное прямое объявление и возможность использовать настраиваемый базовый тип (не int по умолчанию).
однако точка с запятой после пространств имен не требуется. это опечатка?
Как можно «принять» ответ на такой вопрос? Без оскорблений к кшахару, это хорошая техника, но «принятие» ответа автоматически выводит его на первое место и не позволяет проводить более демократичный процесс оценки, что является позором для «что вам больше всего нравится ...» вопрос
Я согласен с этим, за исключением того, что я обычно делаю это как классы. (Это потому, что у меня есть некоторая генерация кода, которая позволяет мне автоматически преобразовывать перечисление в строку и строку в перечисление.)
теперь пришло время строго типизированного перечисления :)
re: ididak
Я исправляю код, который разбивает длинные операторы на слишком много коротких строк.
Посмотрим правде в глаза: это уже не 90-е. Если ваша компания не может позволить себе широкоформатные ЖК-дисплеи для своих кодеров, вам нужно найти лучшую работу :)
Даже если ваш монитор полностью обернут вокруг вашей головы, вам будет легче воспринимать более короткие строки. Пусть контекст кода определяет, должна ли строка быть длинной; обычно это не так.
Я кладу несколько окон рядом на широкие экраны. Я также хотел бы иметь возможность (читать) код на ноутбуке.
В большинстве случаев нет необходимости видеть всю эту действительно длинную строку, чтобы сразу понять, что она делает.
Ладно, сделаем 120 и договоримся :)
Вы получаете мой голос +1. Бывают случаи, когда перенос длинных строк действительно помогает, но автоматически переносить строку только потому, что она достигает отметки 80 столбцов, просто глупо.
Слишком длинные строки в книгах - хороший повод не читать книгу. Это уже не 90-е, правда, но с тех пор у человека не было столько времени, чтобы развиваться, чтобы мозг не раздражали слишком длинные строки.
Минус здесь. Как уже сказал ididak, иногда необходимо разместить несколько окон кода на одном экране рядом. Рассмотрим утилиты сравнения / слияния кода. Ваши «исправления» напомнят людям обо всех удобствах горизонтальной прокрутки.
Параллельные буферы (у меня обычно три), различия (в терминалах, gitk, веб-интерфейсах, таких как github, ...), трудность для моих глаз, чтобы перейти от строки к следующей и приземлиться в нужном месте , предупреждение о том, что ваш код имеет слишком много уровней отступов и нуждается в рефакторинге (обоснование использования ядра Linux на 80 пределов и 8-ми пробелами табуляции), ...
Мне очень нравится помещать небольшой оператор в одну строку с if
int myFunc(int x) {
if (x >20) return -1;
//do other stuff ....
}
Ага, подходит для охранных оговорок. Тем не менее, я всегда заключаю условные скобки в фигурные скобки. Для меня это оказалось более удобным в обслуживании.
Я обычно не использую фигурные скобки, если делаю все в одной строке. Если линия оказывается достаточно длинной, чтобы я хотел ее разорвать, я добавляю фигурные скобки.
Еще более читаемым будет: return -1 if x > 20;
Возможно, более читабельный, но это не C++ ..
Обычно я не согласен с этим предложением, так как оно делает отладку (пошаговое выполнение кода шаг за шагом) немного сложнее - потому что бывают случаи, когда я не знаю, была ли эта строка выполнена. Я должен больше думать (сила разума) в анализе кода, чтобы увидеть, что происходит. С многострочностью мне не нужно анализировать, я просто вижу, что код проходит «внутрь» или «завершается», и если это не так, как ожидалось, я только тогда начинаю анализировать.
Я всегда придираюсь и редактирую следующее:
Полезно помещать имена функций в новую строку, чтобы вы могли использовать grep как
grep -R '^fun_name' .
для них. Я видел, что этот стиль используется для множества проектов GNU, и он мне нравится:
static void
fun_name (int a, int b) {
/* ... */
}
Альтернатива, которая не требует этого соглашения: egrep -r 'main\([^)]*\)\s?\{' . - по общему признанию, не так прост, и требует аргументов и открывающей скобки в одной строке.
Может быть, пора написать специальный grep для кода C++, который использует libclang для разбора.
Я обычно придерживаюсь KNF, описанного в * BSD СТИЛЬ (9)
После работы с кем-то, кто был частично слепым - и по его просьбе - я переключился на использование гораздо большего количества пробелов. Тогда мне это не нравилось, но теперь я предпочитаю это. Вне всяких сомнений, единственное место, где нет пробелов между идентификаторами и ключевыми словами и еще много чего, - это после - имя функции и перед следующими скобками.
void foo( int a, int b )
{
int c = a + ( a * ( a * b ) );
if ( c > 12 )
c += 9;
return foo( 2, c );
}
Один парень на моей работе ставит пробел перед точкой с запятой. Сводит меня с ума.
На работе я такой парень.
Я сам начал делать это в последние пару месяцев, думаю, в целом это делает код более читабельным, в том числе и менее загроможденным. Хотя в качестве придирки я использую фигурные скобки после ВСЕХ операторов if. :П
Доступность вместо того, чтобы выглядеть кратко
Я склонен ставить другое на все свои «если».
if (condition)
{
complicated code goes here
}
else
{
/* This is a comment as to why the else path isn't significant */
}
Хотя это раздражает моих коллег. С первого взгляда можно сказать, что во время кодирования я учел другой случай.
Если код в истинном случае настолько сложен, вероятно, это должна быть отдельная функция / метод. Это уменьшит потребность в бесполезном блоке else и меньше раздражает ваших коллег.
Я обычно вставляю это как комментарий
Мне кажется, вы предпочли бы haskell, где всегда нужно указывать обе стороны.
@robert, я предпочитаю нативный код.
@EvilTeach Я не понимаю вашей точки зрения, потому что Haskell компилируется в собственный код почти так же, как и C, и работает почти со скоростью C?
RAII может быть самой важной идиомой. Идея заключается в том, что ресурсы должны быть сопоставлены с объектами, чтобы время их жизни управлялось автоматически в соответствии с областью, в которой эти объекты объявлены.
Например, если дескриптор файла был объявлен в стеке, он должен быть неявно закрыт, как только мы вернемся из функции (или цикла, или какой бы области действия он ни был объявлен внутри). Если распределение динамической памяти было выделено как член класса, оно должно быть неявно освобождено при уничтожении этого экземпляра класса. И так далее. Каждый вид ресурса - выделение памяти, дескрипторы файлов, соединения с базой данных, сокеты и любой другой вид ресурсов, который должен быть получен и освобожден, - должен быть заключен в такой класс RAII, время жизни которого определяется областью, в которой он был заявил.
Одним из основных преимуществ этого является то, что C++ гарантирует, что деструкторы вызываются, когда объект выходит за пределы области видимости, независимо от того, как контроль покидает эту область. Даже если возникнет исключение, все локальные объекты выйдут из области видимости, и связанные с ними ресурсы будут очищены.
void foo() {
std::fstream file("bar.txt"); // open a file "bar.txt"
if (rand() % 2) {
// if this exception is thrown, we leave the function, and so
// file's destructor is called, which closes the file handle.
throw std::exception();
}
// if the exception is not called, we leave the function normally, and so
// again, file's destructor is called, which closes the file handle.
}
Независимо от того, как мы выходим из функции и что происходит после открытия файла, нам не нужно явно закрывать файл или обрабатывать исключения (например, try-finally) внутри этой функции. Вместо этого файл очищается, потому что он привязан к локальному объекту, который уничтожается, когда выходит за пределы области видимости.
RAII также менее известен как SBRM (Scope-Bound Resource Management).
Смотрите также:
Я думаю, что нужно что-то об исключениях.
Добавлено примечание об исключениях и пример
Идиома pImpl - очень полезный способ отделить интерфейс класса от его реализации.
Обычно определение класса должно содержать переменные-члены, а также методы, которые могут предоставлять слишком много информации. Например, переменная-член может иметь тип, определенный в заголовке, который мы не хотим включать везде.
Заголовок windows.h является здесь ярким примером. Мы можем захотеть обернуть HANDLE или другой тип Win32 внутри класса, но мы не можем поместить HANDLE в определение класса без необходимости включать windows.h везде, где используется класс.
Тогда решение состоит в том, чтобы создать пrivate IMPLementation или пointer-to-IMPLementation класса, и позволить публичной реализации хранить только указатель на частный и пересылать все методы-члены.
Например:
class private_foo; // a forward declaration a pointer may be used
// foo.h
class foo {
public:
foo();
~foo();
void bar();
private:
private_foo* pImpl;
};
// foo.cpp
#include whichever header defines the types T and U
// define the private implementation class
class private_foo {
public:
void bar() { /*...*/ }
private:
T member1;
U member2;
};
// fill in the public interface function definitions:
foo::foo() : pImpl(new private_foo()) {}
foo::~foo() { delete pImpl; }
void foo::bar() { pImpl->bar(); }
Реализация foo теперь отделена от общедоступного интерфейса, так что
Пользователи класса просто включают заголовок, который не содержит ничего конкретного о реализации класса. Все детали реализации содержатся внутри foo.cpp.
Я бы предложил PIMPL или, как первоначально назвал его Джеймс Коплиен, «Handle Body».
Эта идиома позволяет полностью отделить интерфейс от реализации. При работе над переписыванием и повторным выпуском основного компонента промежуточного программного обеспечения CORBA эта идиома использовалась для полного отделения API от реализации.
Это практически исключило любую возможность обратного проектирования.
Отличный ресурс по идиомам C++ - отличная книга Джеймса Коплиена «Стили и идиомы расширенного программирования на C++». Настоятельно рекомендуется!
Редактировать: Как указано ниже Нилом, эта книга довольно устарела, и многие из его рекомендаций фактически включены в сам стандарт C++. Тем не менее, я по-прежнему считаю, что это источник полезной информации, особенно. в форме его Документ PLoP по идиомам C++, где многие идиомы были преобразованы в форму шаблона.
Книга полностью устарела и мало актуальна для современных программистов на C++.
Это Джеймс Коплиен. Идиома ручки / тела, хотя и схожая, обычно не использовалась одинаково или по тем же причинам. Это было в первую очередь для подсчета ссылок.
Я пропустил ошибку Дугласа Коплиена - на самом деле это довольно забавно. Я бы заплатил за то, чтобы прочитать переписанные микросервисы для обозначения оригинальных разработчиков на C++!
Учитывая, что текущие реализации C++ должны быть обратно совместимы с предыдущими реализациями, я думаю, что ваше утверждение «полностью устарело и мало актуально для современных программистов на C++» будет немного преувеличением. В этой книге еще много полезного. Ой. Должно быть, перепутал Дуга Коупленда с Коупом.
Книга @Rob Coplien вышла почти 20 лет назад, задолго до того, как язык был стандартизирован, и стандарт не давал никаких гарантий, что он совместим со старыми реализациями - во многих случаях это было явно не так. Кроме того, многие идиомы, упоминаемые Коплиеном, были включены в языковые функции, такие как шаблоны.
@Neil, я согласен, поэтому я добавил примечание о возрасте книги и ссылку на его обновление книги 98 года.
@ Нил: Не могу сказать, что полностью согласен. Немало кода в книге зависит от шаблонов (хотя по современным меркам его использование довольно "ручное"). Самая большая часть, которая устарела, - это повсеместное использование подсчета ссылок. Я бы сказал, что около 2/3 идиом из книги применимы сегодня, как и когда-либо (хотя сегодня вы часто применяете их несколько иначе).
@Jerry Использование шаблонов сначала появляется на странице 259 (они кратко упоминаются перед этим), что составляет примерно 2/3 от основного текста, и пример, как вы говорите, «ручной», некоторые могут сказать «наивный» . Но я полагаю, что должен заявить, что мне никогда не нравилась эта книга, и эта многолетняя неприязнь меня окрашивает мой взгляд. Мне все еще очень трудно представить, чтобы порекомендовать его новому программисту на C++ в 2010 году.
CRTP происходит, когда вы передаете класс в качестве параметра шаблона его базовому классу:
template<class Derived>
struct BaseCRTP {};
struct Example : BaseCRTP<Example> {};
Внутри базового класса он может получить производный экземпляр, в комплекте с производным типом, просто путем преобразования (работает либо static_cast, либо dynamic_cast):
template<class Derived>
struct BaseCRTP {
void call_foo() {
Derived& self = *static_cast<Derived*>(this);
self.foo();
}
};
struct Example : BaseCRTP<Example> {
void foo() { cout << "foo()\n"; }
};
Фактически, call_foo был впрыснутый в производном классе с полным доступом к членам производного класса.
Не стесняйтесь редактировать и добавлять конкретные примеры использования, возможно, в другие сообщения SO.
Если когда-либо идиома имела плохую репутацию (а в C++ есть некоторые люлусы), то это должно быть так. Я думаю, вам следует описать хотя бы одно из любопытных применений, чтобы этот ответ был полезным.
Это называется повторяющимся шаблоном Curious_ly_. Это наречие: любопытно повторение, а не закономерность :-)
@ Роджер Пейт: Зачем публиковать ответ и голосовать за закрытие?
Я проголосовал за закрытие, прежде чем вопрос резко изменился.
Как это может быть твоим любимым? Я использовал его, но каждый раз, когда я перешагиваю через него, мне приходится думать с самого начала. Ненужно сложное ИМХО.
@ Nils Performance! self.foo отправляется статически в Example, тогда как вызов метода virtual отправляется динамически, что является медленнее, если вы педантично относитесь к микрооптимизациям!
Идиома copy-swap обеспечивает безопасное копирование. Это требует, чтобы были реализованы корректный ctor копирования и своп.
struct String {
String(String const& other);
String& operator=(String copy) { // passed by value
copy.swap(*this); // nothrow swap
return *this; // old resources now in copy, released in its dtor
}
void swap(String& other) throw() {
using std::swap; // enable ADL, defaulting to std::swap
swap(data_members, other.data_members);
}
private:
Various data_members;
};
void swap(String& a, String& b) { // provide non-member for ADL
a.swap(b);
}
Вы также можете реализовать метод подкачки с помощью ADL (Argument Dependent Lookup) напрямую.
Эта идиома важна, потому что она обрабатывает самоназначение [1], обеспечивает строгую гарантию исключения [2] и часто очень проста в написании.
[1] Несмотря на то, что самоназначение обрабатывается не настолько эффективно, насколько это возможно, предполагается, что это редкий, поэтому, если этого никогда не произойдет, на самом деле это будет быстрее.
[2] Если возникает какое-либо исключение, состояние объекта (*this) не изменяется.
Возможно, какой-то текст, объясняющий, почему / как / когда это полезно?
Есть ли причина для "использования std :: swap;" вместо просто "std :: swap (data_members, other.data_members);"?
leetNightshade: Таким образом, если член класса также использует идиому копирования и обмена, поиск, зависящий от аргументов, должен вызывать лучше оптимизированную функцию обмена. std :: swap обычно вызывает конструктор копирования (конструктор перемещения в C++ 11 +) и два оператора присваивания, а для копирования и обмена каждый оператор присваивания в любом случае вызывает функцию подкачки, специфичную для класса. Следовательно, использование std :: swap будет стоить вдвое дороже, ПЛЮС стоимость конструктора перемещения или копирования. Это не значит, что копирование и замена делает std :: swap сверхмедленным; специализированная версия просто лучше.
Зачем вам не-членская версия свопа?
@schanq прочитайте комментарий прямо над вашим - так что другие классы в вашей кодовой базе, использующие вышеупомянутую идиому, будут иметь `` лучше оптимизированную функцию подкачки '' для вашего типа (здесь String), доступную для использования вместо std::swap благодаря мощности поиск, зависящий от аргументов, при разрешении swap
throw() устарел, начиная с C++ 11, а с C++ 17 он совпадает с noexcept (en.cppreference.com/w/cpp/language/except_spec)
Я не знаю, можно ли это назвать идиомой, но довольно много сложного программирования шаблонов зависит (часто сильно) от SFINAE (отказ замены не является ошибкой). В паре ответов на предыдущий вопрос есть примеры.
Я думаю, это можно назвать идиомой. Однако вопрос о том, входит ли он в число «самых полезных». Большинству программистов на C++, вероятно, это не нужно очень часто. Тем не менее, +1 от меня.
В сочетании с демонстрацией полезности boost :: enable_if.
(Также известный как синтаксический полиморфизм и статический полиморфизм, в отличие от полиморфизма времени выполнения.)
С помощью шаблонных функций можно написать код, который полагается на конструкторы типов и сигнатуры вызовов семейств параметризованных типов, без необходимости вводить общий базовый класс.
В книге Элементы программирования авторы называют эту обработку типов как абстрактные роды. С помощью концепции можно указать требования к таким параметрам типа, хотя C++ не требует таких спецификаций.
Два простых примера:
#include <stdexcept>
template <typename T>
T twice(T n) {
return 2 * n;
}
InIt find(InIt f, InIt l,
typename std::iterator_traits<InIt>::reference v)
{
while (f != l && *f != v)
++f;
return f;
}
int main(int argc, char* argv[]) {
if (6 != twice(3))
throw std::logic_error("3 x 2 = 6");
int const nums[] = { 1, 2, 3 };
if (nums + 4 != find(nums, nums + 4, 42))
throw std::logic_error("42 should not have been found.");
return 0;
}
Можно вызвать twice с любым обычным типом, для которого определен бинарный оператор *. Точно так же можно вызвать find() с любыми сопоставимыми типами и этой моделью Входной итератор. Один набор кода одинаково работает с разными типами, при этом общих базовых классов в поле зрения нет.
Конечно, на самом деле здесь происходит то, что один и тот же исходный код представляет собой расширенный для различных функций, зависящих от типа, во время создания экземпляра шаблона, каждая с отдельным сгенерированным машинным кодом. Принятие того же набора типов без шаблонов потребовало бы либо 1) отдельных рукописных функций с определенными сигнатурами, либо 2) полиморфизма времени выполнения через виртуальные функции.
Это способ обработать как можно больше во фреймворке и предоставить дверь или крюк для настройки пользователями фреймворка. Также известен как Hotspot и Шаблонный метод.
class Class {
void PrintInvoice(); // Called Template (boilerplate) which uses CalcRate()
virtual void CalcRate() = 0; // Called Hook
}
class SubClass : public Class {
virtual void CalcRate(); // Customized method
}
Описан Вольфганг Пре в его книге Шаблоны проектирования для объектно-ориентированной разработки программного обеспечения.
Херб Саттер приводит веские доводы в пользу того, что виртуальные методы обычно должны быть частный, поскольку это позволяет базовому классу применять инварианты (например, всегда выполнять методы производного класса в определенном порядке): gotw.ca/publications/mill18.htm
if (expression) // preferred - if keyword sticks out more
против.
if (expression) // looks too much like a void function call
Я предполагаю, что это означает, что мне нравится, что мои вызовы функций НЕ имеют разделителя пробелов
foo(parm1, parm2);
Как бы то ни было, стандарт кодирования llvm согласен с вами, но перечисляет его в разделе «Микроскопические детали».
Публичный верх - частный даун
На первый взгляд это небольшая оптимизация, но с тех пор, как я перешел на это соглашение, у меня стало намного веселее изучать свои классы, особенно после того, как я не смотрел на них в течение 42 лет.
Чрезвычайно полезно иметь постоянную видимость участников, начиная от часто интересных и заканчивая скучными вещами, особенно когда код должен быть самодокументированным.
(примечание для qt-пользователей: слоты идут перед сигналами, потому что они должны быть вызываемыми, как функции-члены, не являющиеся слотами, и, помимо их слотности, быть неотличимы от не-слотов)
private:, идут данные (в идеале только импли-указатель).Это правило также очень помогает, если у вас есть проблемы с сохранением незагроможденного объявления класса.
class Widget : public Purple {
public:
// Factory methods.
Widget FromRadians (float);
Widget FromDegrees (float);
// Ctors, rule of three, swap
Widget();
Widget (Widget const&);
Widget &operator = (Widget const &);
void swap (Widget &) throw();
// Member methods.
float area() const;
// in case of qt {{
public slots:
void invalidateBlackHole();
signals:
void areaChanged (float);
// }}
protected:
// same as public, but for protected members
private:
// same as public, but for private members
private:
// data
float widgetness_;
bool isMale_;
};
Лучше выложить свой пример в качестве ответа ;-)