Каковы ваши любимые идиомы стиля С++ Coding

Каковы ваши любимые идиомы стиля кодирования на С++? Я спрашиваю о типографике стиля или кодировании, например, когда вы помещаете фигурные скобки, есть ли пробелы после ключевых слов, размер отступов и т.д. Это противоречит лучшим практикам или требованиям, таким как всегда удаление массивов с помощью delete[].

Вот пример одного из моих фаворитов: в инициализаторах класса С++ мы помещаем разделители в начале строки, а не обратно. Это упрощает обновление данных. Это также означает, что управление исходным кодом отличается между версиями более чистыми.

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 ] )
{
    ;
}

Ответы

Ответ 1

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

namespace EntityType {
    enum Enum {
        Ground = 0,
        Human,
        Aerial,
        Total
    };
}

void foo(EntityType::Enum entityType)
{
    if (entityType == EntityType::Ground) {
        /*code*/
    }
}

EDIT. Однако этот метод устарел в С++ 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).

Ответ 2

RAII: Приобретение ресурсов инициализируется

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

Например, если дескриптор файла был объявлен в стеке, он должен быть неявно закрыт, как только мы вернемся из функции (или цикла, или в зависимости от того, какая область была объявлена ​​внутри). Если распределение динамической памяти было назначено как член класса, оно должно быть освобождено неявно, когда этот экземпляр класса будет уничтожен. И так далее. Каждый вид ресурсов, памяти, файлов, соединений с базами данных, сокетов и любого другого ресурса, который должен быть приобретен и выпущен, должен быть обернут внутри такого класса RAII, срок жизни которого определяется областью, в которой он был объявлено.

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

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 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 destructor is called, which closes the file handle.
}

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

RAII также менее известен как SBRM (управление ресурсами с ограничением объема).

См. также:

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

Ответ 3

Copy-своп

Идиома copy-swap обеспечивает безопасное копирование. Это требует, чтобы были выполнены правильные копии ctor и swap.

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 (зависимым от аргумента) .

Эта идиома важна, поскольку она обрабатывает самоопределение [1] делает сильное исключение гарантией [2] и часто очень легко писать.


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

[2] Если какое-либо исключение выбрано, состояние объекта (*this) не изменяется.

Ответ 4

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.

Ответ 5

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

Идиома pImpl - очень полезный способ отделить интерфейс класса от его реализации.

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

Заголовок windows.h - яркий пример. Возможно, мы захотим обернуть HANDLE или другой тип Win32 внутри класса, но мы не можем поместить HANDLE в определение класса без необходимости включать windows.h везде, где используется класс.

Тогда решение должно создать P rivate IMPL ementation или P ointer-to IMPL ementation класса, и пусть публичная реализация сохраняет только указатель на частный и пересылает все методы-члены.

Например:

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.

Ответ 6

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

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

Ответ 7

Public Top - Private Down

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

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

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

  • Открытый, защищенный, закрытый
  • затем 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_;
};

Ответ 8

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

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

Ответ 9

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

  • вкладки - вызывает несоосность во многих IDE и инструментах проверки кода, потому что они не всегда соглашаются на вкладку в пространствах mod 8.
  • линии длиной более 80 столбцов - пусть это будет выглядеть, более короткие строки более читабельны. Мой мозг может анализировать большинство соглашений о кодировании, если строки коротки.
  • линии с завершающими пробелами - git будет жаловаться на это как на пробельные ошибки, которые отображаются как красные blobs в diffs, что раздражает.

Вот однострочный файл для поиска файлов-нарушителей:

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

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

Ответ 10

re: ididak

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

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

Ответ 11

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

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

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

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

Два простых примера:

#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() с любыми сопоставимыми типами, а также с моделью ввода Iterator. Один набор кодов работает аналогично на разных типах, без общих классов базы данных.

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

Ответ 12

Шаблон и крюк

Это способ как можно более эффективно обрабатывать фреймворк и давать пользователю или фреймворку дверь или крючок для настройки. Также известен как Hotspot и Template Method.

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
}

Описан Wolfgang Pree в своей книге Шаблоны проектирования для объектно-ориентированных Разработка программного обеспечения.

Ответ 13

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

Ответ 14

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

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

против.

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

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

foo(parm1, parm2);

Ответ 15

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

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

Ответ 16

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

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

Ответ 17

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

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

(в сторону. Мои комментарии обычно не такие отстойные.)

Ответ 18

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

grep -R '^fun_name' .

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

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

Ответ 19

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

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

Ответ 20

Я бы предложил PIMPL или как Джеймс Коплиен изначально называл это "Handle Body".

Эта идиома позволяет полностью отделить интерфейс от реализации. При работе над переписыванием и переизданием основного компонента промежуточного программного обеспечения CORBA эта идиома использовалась для полного отстранения API от реализации.

Это практически исключало любую возможность обратной инженерии.

Отличным ресурсом для идиом С++ является превосходная книга Джеймса Коплиена " Расширенные стили и идиомы программирования на C++". Очень рекомендуется!

Изменить: Как было указано ниже Нилом, эта книга совершенно устарела, многие из его рекомендаций фактически включены в сам стандарт С++. Тем не менее, я все еще считаю, что это источник полезной информации, особенно. в виде его документа PLoP на идиомах С++, где многие идиомы были переделаны в форму patterm.

Ответ 21

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

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

Ответ 22

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

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

Ответ 23

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

Ответ 24

Я склоняюсь к тому, чтобы поставить все на все мои ifs.

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

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