C++ Best Practices
От: Iso12  
Дата: 19.06.17 13:03
Оценка: 2 (1)
Интересный и познавательный C++ Best Practices от Jason Turner.
Re: C++ Best Practices
От: N. I.  
Дата: 19.06.17 16:12
Оценка: 3 (1) +4
I>Интересный и познавательный C++ Best Practices от Jason Turner.

Not again...

Очередная куча спорных советов вперемешку с более-менее рациональными. Типа решаем одни проблемы ценой появления взамен других, о которых скромно умалчиваем (то ли неявно считая их недостойными внимания, то ли игнорируя надобность в поиске и анализе недостатков избранного подхода в принципе), либо рассказываем о вещах, которые и так достаточно очевидны для любого мало-мальски опытного кодера с головой на плечах. По мне так шли бы все такие господа советчики далёким лесом со своими "best practices".
Re[2]: C++ Best Practices
От: Iso12  
Дата: 20.06.17 08:45
Оценка:
Здравствуйте, N. I., Вы писали:


NI>Очередная куча спорных советов вперемешку с более-менее рациональными. Типа решаем одни проблемы ценой появления взамен других, о которых скромно умалчиваем (то ли неявно считая их недостойными внимания, то ли игнорируя надобность в поиске и анализе недостатков избранного подхода в принципе), либо рассказываем о вещах, которые и так достаточно очевидны для любого мало-мальски опытного кодера с головой на плечах. По мне так шли бы все такие господа советчики далёким лесом со своими "best practices".


Учиться на опыте других, всегда полезно и нужно. Это не аксиомы, в которые нужно тупо верить, а рекомендации, которые человек написал из своего опыта. Подход к программированию у каждого свой и не всегда совпадает. Берите в свой багаж знаний то, что считаете нужным.
Интерсно было бы узнать, что там конкретно не так на ваш взгляд.
Re[3]: C++ Best Practices
От: uzhas Ниоткуда  
Дата: 20.06.17 16:15
Оценка: +1
Здравствуйте, Iso12, Вы писали:

I>Учиться на опыте других, всегда полезно и нужно.


тут есть небольшая проблема
1) в отсутствии авторитета у автора этих рекомендаций. с какого он района кто это такой?
2) сырая книга, какой-то поток сознания без внятных аргументаций. сиди и додумывай
Re[3]: C++ Best Practices
От: N. I.  
Дата: 20.06.17 17:14
Оценка: 35 (5) +1
I>Интерсно было бы узнать, что там конкретно не так на ваш взгляд.

Не хочется тратить время на разбор всего, поэтому пробегусь по советам выборочно:

1.

Don't Name Anything Starting With _


Вот прям anything?

Ну что ж, начнём с того, что для user-defined literals, определяемых пользователем, следовало бы дать прямо противоположную рекомендацию, т.к. идентификаторы суффиксов литералов, которые не начинаются с подчёркивания, зарезервированы для использования в будущих стандартах и попытка определения функций пользовательских литералов с такими суффиксами повлечёт compile-time undefined behavior:

C++11/14 [usrlit.suffix] p1:

Literal suffix identifiers that do not start with an underscore are reserved for future standardization.


C++11/14 [lex.ext] p10:

Some identifiers appearing as ud-suffixes are reserved for future standardization (17.6.4.3.5). A program containing such a ud-suffix is ill-formed, no diagnostic required.


C++11/14 [intro.compliance] p2.3:

If a program contains a violation of a rule for which no diagnostic is required, this International Standard places no requirement on implementations with respect to that program.


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

2.

Comment blocks should use //, not /* */. Using // makes it much easier to comment out a block of code while debugging.


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

Для временного исключения кода можно воспользоваться препроцессорными директивами #if и #endif:

#if 0
    <inactive code>
#endif

3.

Never Use using namespace in a Header File


Ну прям never? А как насчёт случая, когда пространство имён вводится только ради предотвращения нахождения некоторых функций через ADL?

 // header file
namespace N1
{
    class X
    {
        ....
    };

    namespace noninterface_functions {}
    using namespace noninterface_functions;

    namespace noninterface_functions
    {
        template <class T>
            void f(T const &x) { .... }
    }
}

namespace N2
{
    void g(N1::X &arg)
    {
        N1::f(arg); // OK, uses N1::noninterface_functions::f
        f(arg); // won't find N1::noninterface_functions::f
    }
}

Здесь цель noninterface_functions не в том, чтобы заставить пользователя f писать N1::noninterface_functions::f(arg) вместо N1::f(arg), а в том, чтобы отделить f от интерфейса класса X таким образом, чтоб f не могла быть найдена через ADL, когда при вызове без квалификации туда передают аргумент, тип которого ассоциирован с X.

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

4.

{} Are Required for Blocks.

Leaving them off can lead to semantic errors in the code.

// Bad Idea
// The cout is not part of the loop in this case even though it appears to be.
int sum = 0;
for (int i = 0; i < 15; ++i)
  ++sum;
  std::cout << i << std::endl;


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

5.

Keep Lines a Reasonable Length

// Bad Idea
// hard to follow
if (x && y && myFunctionThatReturnsBool() && caseNumber3 && (15 > 12 || 2 < 3)) {
}

// Good Idea
// Logical grouping, easier to read
if (x && y && myFunctionThatReturnsBool()
    && caseNumber3
    && (15 > 12 || 2 < 3)) {
}


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

6.

Initialize Member Variables

...with the member initializer list.

// Bad Idea
class MyClass
{
public:
  MyClass(int t_value)
  {
    m_value = t_value;
  }

private:
  int m_value;
};


Хоть в данной ситуации я бы и сам выбрал member initializer list, в использовании здесь присваивания я как-то не вижу особого криминала. А будь m_value какой-нибудь структурой-агрегатом наподобие

struct SequenceInfo
{
    int index;
    int size;
} m_value {};

то вот такой вариант

MyClass::MyClass(int sequence_size)
{
    m_value.index = 0;
    m_value.size = sequence_size;
    ....
}

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

MyClass::MyClass(int sequence_size) :
    m_value{0, sequence_size}
{
    ....
}

7.

Never Put Code with Side Effects Inside an assert()

assert(registerSomeThing()); // make sure that registerSomeThing() returns true


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

8.

Avoid Implicit Conversions

Single parameter constructors can be applied at compile time to automatically convert between types. This is handy for things like std::string(const char *) but should be avoided in general because they can add to accidental runtime overhead.

Instead mark single parameter constructors as explicit, which requires them to be explicitly called.


То есть давайте откажемся от удобных неявных преобразований из принципа "а то как бы чего плохого не вышло"?

9.

Const as Much as Possible

// Bad Idea
class MyClass
{
public:
  void do_something(int i);
  void do_something(std::string str);
};


// Good Idea
class MyClass
{
public:
  void do_something(const int i);
  void do_something(const std::string &str);
};


Объявления

void do_something(int i);

и

void do_something(const int i);

семантически эквивалентны друг другу. В обоих случаях функция do_something имеет тип void(int) и далее её можно определить как с "const int i", так и с "int i". Изменчивость или постоянство i — это деталь реализации do_something, и в демонстрации этой детали реализации на уровне интерфейса MyClass, по-моему, какой-то заметной нужды нет. Функция вполне может захотеть менять этот самый i после небольшой переделки. Теперь насчёт

void do_something(std::string str);

Если внутри этой do_something мы собираемся сделать move из str, то такой вариант объявления str с точки зрения производительности может оказаться выигрышнее, чем вариант со ссылкой на const, по крайней мере для некоторых возможных аргументов.

10.

Use Initializer Lists

// This
std::vector<ModelObject> mos{mo1, mo2};

// -or-
auto mos = std::vector<ModelObject>{mo1, mo2};

// Don't do this
std::vector<ModelObject> mos;
mos.push_back(mo1);
mos.push_back(mo2);


Initializer lists are significantly more efficient; reducing object copies and resizing of containers.


Эффективность явно не относится к числу достоинств std::initializer_list, и с копированиями там как раз-таки всё очень печально. std::initializer_list<ModelObject> не просто хранит ссылки на то, что мы указываем в списке инициализации, а конструирует последовательность из вполне себе реальных объектов типа const ModelObject, используя наш список {mo1, mo2} в качестве соответствующих элементам последовательности инициализаторов. Поскольку все созданные объекты константные, про перемещение move-конструктором от non-const можно забыть — всю последовательность элементов std::initializer_list<ModelObject> нашему std::vector<ModelObject> mos придётся в себя копировать. Если тип ModelObject поддерживает move construction, но не поддерживает copy construction, то варианты с использованием initializer_list просто не соберутся, в то время как вариант с push_back мог бы быть работоспособным. Если же copy construction поддерживается, то при задействовании std::initializer_list извольте получить копирование всей последовательности. Резервирование памяти под нужное количество элементов с последующей вставкой элементов через серию emplace_back может быть существенно более оптимальным решением:

std::vector<ModelObject> mos;
mos.reserve(2);
mos.emplace_back(mo1);
mos.emplace_back(mo2);

Наглядный пример с демонстрацией аллокаций памяти для std::vector<std::string>: https://wandbox.org/permlink/XPpXuPrQW3wA7Br1

Пожалуй, на этом хватит.
Отредактировано 20.06.2017 17:47 N. I. . Предыдущая версия . Еще …
Отредактировано 20.06.2017 17:27 N. I. . Предыдущая версия .
Re[4]: C++ Best Practices
От: landerhigh Пират  
Дата: 22.06.17 09:44
Оценка:
Здравствуйте, N. I., Вы писали:

NI>5.

Keep Lines a Reasonable Length


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


А вот тут есть такая вещь. Человеку удобнее читать строки не длиннее вроде бы 60 символов. Это давно известное типографское правило, без всяких "по-моему". Необходимость перемещать взгляд по вертикали выбивает из рутины.
Другое дело, что код — это не текст. И по большому счету пофигу, как организовывать длинное выражение, в него все равно придется при случае вникать. Ну и не стоит сбрасывать со счетов классический случай с горизонтальными скроллбарами, когда в наличии только узкий монитор.
www.blinnov.com
Re[4]: C++ Best Practices
От: Vaynamond Россия  
Дата: 29.06.17 16:44
Оценка:
Здравствуйте, N. I., Вы писали:

NI>Для временного исключения кода можно воспользоваться препроцессорными директивами #if и #endif:


NI>
NI>#if 0
NI>    <inactive code>
NI>#endif


А я наоборот использую /**/. Преимущества — подсветка синтаксиса.
Re[5]: C++ Best Practices
От: N. I.  
Дата: 29.06.17 19:13
Оценка:
Vaynamond:

V>А я наоборот использую /**/. Преимущества — подсветка синтаксиса.


Подсветка закомменченного кода как раз-таки ломается. Некоторые современные IDE (например, VS, CB и Eclipse + CDT) умеют выделять неактивный код за счёт применения полупрозрачности или специального фона, сохраняя подсветку синтаксиса. Кроме того, #if очень удобен для переключения между двумя ветками кода:

#if 0
    <inactive code>
#else
    <active code>
#endif

легко превращается в

#if 1
    <active code>
#else
    <inactive code>
#endif

и обратно. Правка происходит всего в одном месте — нужно просто поправить циферку. А в скольких местах придётся править комменты?
Re[6]: C++ Best Practices
От: Vaynamond Россия  
Дата: 29.06.17 20:02
Оценка:
Здравствуйте, N. I., Вы писали:

NI>Vaynamond:


V>>А я наоборот использую /**/. Преимущества — подсветка синтаксиса.


NI>Подсветка закомменченного кода как раз-таки ломается. Некоторые современные IDE (например, VS, CB и Eclipse + CDT) умеют выделять неактивный код за счёт применения полупрозрачности или специального фона, сохраняя подсветку синтаксиса. Кроме того, #if очень удобен для переключения между двумя ветками кода:


NI>
#if 0
NI>    <inactive code>
NI>#else
NI>    <active code>
NI>#endif

NI>легко превращается в

NI>
#if 1
NI>    <active code>
NI>#else
NI>    <inactive code>
NI>#endif

NI>и обратно. Правка происходит всего в одном месте — нужно просто поправить циферку. А в скольких местах придётся править комменты?

А я по-старинке: Far+Colorer
Вся мощь (без иронии) IDE используется, преимущественно, при отладке.

P.S.: Условная компиляция в моей практике, в основном, зависит от соответствующих дефайнов.
Re[7]: C++ Best Practices
От: night beast СССР  
Дата: 30.06.17 05:58
Оценка:
Здравствуйте, Vaynamond, Вы писали:

V>А я по-старинке: Far+Colorer

V>Вся мощь (без иронии) IDE используется, преимущественно, при отладке.

и как Far+Colorer подсвечивает вложенные /* */?
Re[8]: C++ Best Practices
От: Vaynamond Россия  
Дата: 30.06.17 07:18
Оценка:
Здравствуйте, night beast, Вы писали:

NB>Здравствуйте, Vaynamond, Вы писали:


V>>А я по-старинке: Far+Colorer

V>>Вся мощь (без иронии) IDE используется, преимущественно, при отладке.

NB>и как Far+Colorer подсвечивает вложенные /* */?

Есть глюки, но мне не критично. Блоки #ifdef/#endif он вообще не видит
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.