Какие ваши любимые идиомы стиля программирования C++

Какие ваши любимые идиомы стиля программирования 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 ] )
{
    ;
}

Лучше выложить свой пример в качестве ответа ;-)

Leon Timmermans 09.11.2008 21:01

Это практически дубликат stackoverflow.com/questions/66268/… или stackoverflow.com/questions/145570/….

Aidan Ryan 09.11.2008 21:10

Здесь вы говорите о стиле программирования, а не о идиомах.

Nemanja Trifunovic 10.11.2008 02:31
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
65
3
25 862
24
Перейти к ответу Данный вопрос помечен как решенный

Ответы 24

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

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 сказал мне примерно столько же, сколько и имя переменной - ничего (да, я знаю, что это всего лишь пример, но он не очень хороший).

Andreas Magnusson 10.11.2008 14:10

Полностью согласен с Андреасом. Если вам действительно нужно документировать свои параметры, используйте формат Doxygen.

Austin Ziegler 18.11.2008 22:18

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

int function(void) /* return 1 on success, 0 on failure */ 
{
    return 1;
};

Установка лишней точки с запятой в конце блока, в котором точка с запятой не требуется, - одна из моих наименее любимых идиом стиля кодирования C++.

bk1e 09.11.2008 21:39

Это предложение стиля не помогает ни в чем, кроме простейших возвращаемых значений.

Austin Ziegler 18.11.2008 22:19

Ерунда. Вы можете сделать комментарий настолько большим или маленьким, насколько это необходимо. Когда возникает вопрос: «Что это возвращает?» куда ты идешь? руководство пользователя? hpp файл? cpp файл? Если вы посмотрите на функцию, она тут же. Большинство IDE могут выбрать комментарий и показать его вам в контексте.

EvilTeach 23.11.2008 17:52

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

EvilTeach 23.11.2008 17:54

Разве в коде не нужна пара скобок () после function?

Arun 22.10.2010 23:59

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

Johannes Schaub - litb 03.03.2012 22:35

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

EvilTeach 04.03.2012 01:58

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

bool MyObjects::isUpToSomething() ///< Is my object up to something 

(в сторону. Мои комментарии обычно не такие уж неубедительные.)

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

if (  (  (var1A == var2A)
      || (var1B == var2B))
   && (  (var1C == var2C)
      || (var1D == var2D)))
{
   // do something
}

Орфографические ошибки и опечатки также исправляются в коде, над которым я работаю!

Mark Kegel 10.11.2008 03:56

Если бы это был мой код, я бы поставил два || условия в той же строке, но разделите условия &&, как вы это сделали, в результате получится двухстрочный оператор if.

jussij 12.11.2008 02:16

Мне нравится выстраивать код / ​​инициализации в «столбцы» ... Оказывается очень полезным при редактировании с помощью редактора, поддерживающего режим «столбец», а также мне кажется, что его намного легче читать ...

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

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

Eclipse 24.03.2010 22:32

Возможно, вы захотите взглянуть на плагин Align для Vim.

StackedCrooked 05.01.2011 18:15

Нет избранного, но я исправляю код воля, который имеет:

  1. tabs - вызывает несовпадение во многих IDE и инструментах проверки кода, потому что они не всегда соглашаются с табуляцией в пробелах мода 8.
  2. строки длиннее 80 столбцов - давайте посмотрим правде в глаза, более короткие строки читаются лучше. Мой мозг может анализировать большинство условных обозначений кодирования, если строки короткие.
  3. строки с конечными пробелами - git будет жаловаться на них как на пробелы ошибки, которые отображаются как красные капли в различиях, что раздражает.

Вот однострочный способ поиска проблемных файлов:

git grep -I -E '<tab>|.{81,}|  *$' | cut -f1 -d: | sort -u

где <tab> - символ табуляции (регулярное выражение POSIX не выполняет \ t)

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

Terminus 11.11.2008 14:23

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

Owen 18.12.2008 10:53

Проблема с конечным пробелом в том, что вы его не видите.

FryGuy 15.01.2009 01:47
Ответ принят как подходящий

При создании перечислений поместите их в пространство имен, чтобы вы могли получить к ним доступ с осмысленным именем:

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 по умолчанию).

однако точка с запятой после пространств имен не требуется. это опечатка?

Johannes Schaub - litb 10.11.2008 21:55

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

Eli Bendersky 14.11.2008 23:39

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

Austin Ziegler 18.11.2008 22:16

теперь пришло время строго типизированного перечисления :)

Mr.Anubis 16.10.2011 14:54

re: ididak

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

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

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

Mark Ransom 10.11.2008 01:39

Я кладу несколько окон рядом на широкие экраны. Я также хотел бы иметь возможность (читать) код на ноутбуке.

ididak 10.11.2008 03:08

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

korona 11.11.2008 13:49

Ладно, сделаем 120 и договоримся :)

utku_karatas 11.11.2008 14:00

Вы получаете мой голос +1. Бывают случаи, когда перенос длинных строк действительно помогает, но автоматически переносить строку только потому, что она достигает отметки 80 столбцов, просто глупо.

jussij 12.11.2008 02:17

Слишком длинные строки в книгах - хороший повод не читать книгу. Это уже не 90-е, правда, но с тех пор у человека не было столько времени, чтобы развиваться, чтобы мозг не раздражали слишком длинные строки.

Sebastian Mach 21.06.2011 15:33

Минус здесь. Как уже сказал ididak, иногда необходимо разместить несколько окон кода на одном экране рядом. Рассмотрим утилиты сравнения / слияния кода. Ваши «исправления» напомнят людям обо всех удобствах горизонтальной прокрутки.

Alex Che 27.12.2013 14:31

Параллельные буферы (у меня обычно три), различия (в терминалах, gitk, веб-интерфейсах, таких как github, ...), трудность для моих глаз, чтобы перейти от строки к следующей и приземлиться в нужном месте , предупреждение о том, что ваш код имеет слишком много уровней отступов и нуждается в рефакторинге (обоснование использования ядра Linux на 80 пределов и 8-ми пробелами табуляции), ...

Gauthier 28.06.2016 12:28

Мне очень нравится помещать небольшой оператор в одну строку с if

int myFunc(int x) {
   if (x >20) return -1;
   //do other stuff ....
}

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

foraidt 11.11.2008 15:42

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

Adam Jaskiewicz 12.11.2008 02:12

Еще более читаемым будет: return -1 if x > 20;

Pasha 21.05.2013 23:12

Возможно, более читабельный, но это не C++ ..

boycy 12.08.2014 12:57

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

Robert Andrzejuk 20.05.2017 03:44

Я всегда придираюсь и редактирую следующее:

  • Лишние символы новой строки
  • Нет новой строки в EOF

Полезно помещать имена функций в новую строку, чтобы вы могли использовать grep как

grep -R '^fun_name' .

для них. Я видел, что этот стиль используется для множества проектов GNU, и он мне нравится:

static void
fun_name (int a, int b) {
    /* ... */
}

Альтернатива, которая не требует этого соглашения: egrep -r 'main\([^)]*\)\s?\{' . - по общему признанию, не так прост, и требует аргументов и открывающей скобки в одной строке.

Konrad Rudolph 12.11.2008 01:55

Может быть, пора написать специальный grep для кода C++, который использует libclang для разбора.

user1203803 03.03.2012 18:58

Я обычно придерживаюсь KNF, описанного в * BSD СТИЛЬ (9)

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

void foo( int a, int b )
{
  int c = a + ( a * ( a * b ) );
  if ( c > 12 )
    c += 9;
  return foo( 2, c );
}

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

Owen 18.12.2008 10:55

На работе я такой парень.

Stuart Berg 08.01.2012 00:42

Я сам начал делать это в последние пару месяцев, думаю, в целом это делает код более читабельным, в том числе и менее загроможденным. Хотя в качестве придирки я использую фигурные скобки после ВСЕХ операторов if. :П

leetNightshade 11.01.2012 20:09

Доступность вместо того, чтобы выглядеть кратко

shuhalo 02.09.2020 02:13

Я склонен ставить другое на все свои «если».

if (condition)
{
    complicated code goes here
}
else
{
    /* This is a comment as to why the else path isn't significant */ 
}

Хотя это раздражает моих коллег. С первого взгляда можно сказать, что во время кодирования я учел другой случай.

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

Austin Ziegler 18.11.2008 22:20

Я обычно вставляю это как комментарий

franji1 01.10.2010 06:39

Мне кажется, вы предпочли бы haskell, где всегда нужно указывать обе стороны.

Robert Massaioli 01.11.2010 04:57

@robert, я предпочитаю нативный код.

EvilTeach 04.02.2011 04:11

@EvilTeach Я не понимаю вашей точки зрения, потому что Haskell компилируется в собственный код почти так же, как и C, и работает почти со скоростью C?

Robert Massaioli 04.02.2011 04:39

RAII: получение ресурсов - это инициализация

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).

Смотрите также:

  • ScopeGuard позволяет коду «автоматически вызывать операцию отмены ... в случае возникновения исключения».

Я думаю, что нужно что-то об исключениях.

anon 09.01.2010 21:34

Добавлено примечание об исключениях и пример

jalf 09.01.2010 21:48

pImpl: указатель на реализацию

Идиома 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++.

anon 09.01.2010 21:23

Это Джеймс Коплиен. Идиома ручки / тела, хотя и схожая, обычно не использовалась одинаково или по тем же причинам. Это было в первую очередь для подсчета ссылок.

Jerry Coffin 09.01.2010 21:26

Я пропустил ошибку Дугласа Коплиена - на самом деле это довольно забавно. Я бы заплатил за то, чтобы прочитать переписанные микросервисы для обозначения оригинальных разработчиков на C++!

anon 09.01.2010 21:28

Учитывая, что текущие реализации C++ должны быть обратно совместимы с предыдущими реализациями, я думаю, что ваше утверждение «полностью устарело и мало актуально для современных программистов на C++» будет немного преувеличением. В этой книге еще много полезного. Ой. Должно быть, перепутал Дуга Коупленда с Коупом.

Rob Wells 09.01.2010 21:58

Книга @Rob Coplien вышла почти 20 лет назад, задолго до того, как язык был стандартизирован, и стандарт не давал никаких гарантий, что он совместим со старыми реализациями - во многих случаях это было явно не так. Кроме того, многие идиомы, упоминаемые Коплиеном, были включены в языковые функции, такие как шаблоны.

anon 09.01.2010 22:03

@Neil, я согласен, поэтому я добавил примечание о возрасте книги и ссылку на его обновление книги 98 года.

Rob Wells 09.01.2010 22:13

@ Нил: Не могу сказать, что полностью согласен. Немало кода в книге зависит от шаблонов (хотя по современным меркам его использование довольно "ручное"). Самая большая часть, которая устарела, - это повсеместное использование подсчета ссылок. Я бы сказал, что около 2/3 идиом из книги применимы сегодня, как и когда-либо (хотя сегодня вы часто применяете их несколько иначе).

Jerry Coffin 09.01.2010 22:19

@Jerry Использование шаблонов сначала появляется на странице 259 (они кратко упоминаются перед этим), что составляет примерно 2/3 от основного текста, и пример, как вы говорите, «ручной», некоторые могут сказать «наивный» . Но я полагаю, что должен заявить, что мне никогда не нравилась эта книга, и эта многолетняя неприязнь меня окрашивает мой взгляд. Мне все еще очень трудно представить, чтобы порекомендовать его новому программисту на C++ в 2010 году.

anon 09.01.2010 22:27

CRTP: любопытно повторяющийся шаблон шаблона

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

anon 09.01.2010 21:38

Это называется повторяющимся шаблоном Curious_ly_. Это наречие: любопытно повторение, а не закономерность :-)

Roger Lipscombe 09.01.2010 21:41

@ Роджер Пейт: Зачем публиковать ответ и голосовать за закрытие?

jason 09.01.2010 22:45

Я проголосовал за закрытие, прежде чем вопрос резко изменился.

Roger Pate 09.01.2010 22:47

Как это может быть твоим любимым? Я использовал его, но каждый раз, когда я перешагиваю через него, мне приходится думать с самого начала. Ненужно сложное ИМХО.

Nils 06.06.2014 16:22

@ Nils Performance! self.foo отправляется статически в Example, тогда как вызов метода virtual отправляется динамически, что является медленнее, если вы педантично относитесь к микрооптимизациям!

mucaho 29.03.2016 01:28

Копирование-своп

Идиома 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) не изменяется.

Возможно, какой-то текст, объясняющий, почему / как / когда это полезно?

jalf 09.01.2010 21:49

Есть ли причина для "использования std :: swap;" вместо просто "std :: swap (data_members, other.data_members);"?

leetNightshade 11.01.2012 19:18

leetNightshade: Таким образом, если член класса также использует идиому копирования и обмена, поиск, зависящий от аргументов, должен вызывать лучше оптимизированную функцию обмена. std :: swap обычно вызывает конструктор копирования (конструктор перемещения в C++ 11 +) и два оператора присваивания, а для копирования и обмена каждый оператор присваивания в любом случае вызывает функцию подкачки, специфичную для класса. Следовательно, использование std :: swap будет стоить вдвое дороже, ПЛЮС стоимость конструктора перемещения или копирования. Это не значит, что копирование и замена делает std :: swap сверхмедленным; специализированная версия просто лучше.

Mike S 21.08.2014 09:22

Зачем вам не-членская версия свопа?

schanq 19.02.2015 03:30

@schanq прочитайте комментарий прямо над вашим - так что другие классы в вашей кодовой базе, использующие вышеупомянутую идиому, будут иметь `` лучше оптимизированную функцию подкачки '' для вашего типа (здесь String), доступную для использования вместо std::swap благодаря мощности поиск, зависящий от аргументов, при разрешении swap

obataku 05.08.2016 23:42
throw() устарел, начиная с C++ 11, а с C++ 17 он совпадает с noexcept (en.cppreference.com/w/cpp/language/except_spec)
Ale 04.06.2018 23:17

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

Я думаю, это можно назвать идиомой. Однако вопрос о том, входит ли он в число «самых полезных». Большинству программистов на C++, вероятно, это не нужно очень часто. Тем не менее, +1 от меня.

jalf 09.01.2010 22:07

В сочетании с демонстрацией полезности boost :: enable_if.

Roger Pate 09.01.2010 22:13

Полиморфизм времени компиляции

(Также известный как синтаксический полиморфизм и статический полиморфизм, в отличие от полиморфизма времени выполнения.)

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

В книге Элементы программирования авторы называют эту обработку типов как абстрактные роды. С помощью концепции можно указать требования к таким параметрам типа, хотя 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

j_random_hacker 26.06.2012 16:58

if / while / для выражений в скобках с пробелом

if (expression)  // preferred - if keyword sticks out more

против.

if (expression)  // looks too much like a void function call

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

foo(parm1, parm2);

Как бы то ни было, стандарт кодирования llvm согласен с вами, но перечисляет его в разделе «Микроскопические детали».

Stuart Berg 08.01.2012 00:41

Публичный верх - частный даун

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

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

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

  • Общедоступный, защищенный, частный
  • затем Factory, ctor, dtor, копирование, замена
  • затем класс Интерфейс В самом конце, в отдельном разделе 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_;
};

Другие вопросы по теме