Pull to refresh

Comments 213

Много спорного, хотя бы о выравнивании и символы указателя/ссылки. Дополнительное именование указателей часто очень выручает, а выделение переменной класса подчеркиванием вместо логичного this. нелепа.
Хотя конечно каждому своё, кому то наоборот что я перечислил кажется удобным, а называть переменные tHiSiSvArIaBlE удобным.
Да, мне и самому некоторые моменты кажутся спорными (например, пункт № 48). Но документ довольно целостный, как мне кажется.
Не согласен только с 11 пунктом. Очень многие привыкли, что перед скрытыми переменными добавляется 'm' (mDepth), к тому же все сеттеры должны иметь основной параметр с именем value, иначе получается тавтология.

void setDepth (int value)
{
mDepth = value;
}
Не очень понял, почему заминусовали человека.
Префикс «m» у скрытых членов класса — достаточно популярный coding style, и многим, включая меня, он кажется удобным. А если Ваша IDE имеет средства автодополнения, то, написав «m», Вы сразу получаете список всех скрытых членов класса. С постфиксом "_" такого не получится, да и, как кажется мне, он куда менее нагляден.

К слову, по причине использования многими программистами IDE с автодополнениями, я также рекомендую везде писать get/set, т.к. можно просто вбить «get» и получить список всех функции класса, которые возвращают значения.
Это изначально спорный вопрос – сколько людей, столько и мнений. Если посмотреть проекты на github, то много кто использует именно такую нотацию (например вот, библиотека folly от facebook).
Я не спорю с этим. Например, Скотт Майерс тоже пишет с постфиксом "_". Другое дело, что использование префиксов выглядит куда логичнее — это первые буквы при наборе переменной, а, значит, во всяких списках переменных классов они будут удобно расположены и отсортированы в одном месте. Можно использовать _member, можно mMember, можно m_member, но во всех случаях первый символ у Вас однозначно будет указывать на внутренний член класса. Это правда дико удобно.
_member нельзя использовать — подчеркивание в начале слово зарезервивровано для компилятора… А для списка всех членов в текстовом редакторе открывается второе окно с описанием класса. Никто из динозавров IDE не использует.
Вроде бы зарезервированы идентификаторы, начинающиеся с двух подчёркиваний или подчёркивания и заглавной буквы. В boost подчёркивание ставят в конце, т. е. member_.
Зарезервированы идентификаторы:
* содержащие два подчёркивания подряд где угодно,
* начинающиеся с подчёркивания и большой буквы,
* в глобальном namespace — начинающиеся с подчёркивания,
* имена из стандартной библиотеки.
Ну да, получается, что _member — вполне себе адекватное имя для члена класса, ибо не global namespace.
Я не сиплюсплюсник, но есть некоторые возражения. Первые символы действительно важны для группировки. Они отражают то, как мыслит программист. И хотелось бы, чтобы мысли вращались вокруг предметной области, а не реализации. Т.е. важнее то, что значит переменная, а не то, где она находится и ее тип.

А вообще, очень бы неплохо было бы, чтобы IDE поддерживали не просто выпадающий список, но и «кнопки» для группировки. Т.е. выпадающий список по умолчанию, но сверху (снизу) тулбар, с «private», «static» и т.д.
Никак не могу уловить суть рекомендации 93. Комментарии следует располагать так, чтобы они относились к тому, что они описывают.
Мне кажется, что в примере оба варианта (рекомендуемый и не рекомендуемый) одинаковы. Или я чего-то не замечаю…
Обратите внимание на вторую строку.
Спасибо, действительно не заметил отступ… Хотя смысл рекомендации для меня очевиднее не стал.

Кстати, некоторые рекомендации можно было бы поправить с учетом С++11, например:
— 31. Константы в перечислениях могут иметь префикс — общее имя типа: используйте enum class;
— 66. Константы с плавающей точкой следует записывать с десятичной точкой и с указанием по крайней мере одной цифры после запятой: используйте литералы;
— 70. Следует использовать «0» вместо «NULL»: используйте nullptr.
Документ 2011 года, но редактировать я его не стал. Сам пользуюсь теми вариантами, которые указаны Вами.
Учусь в институте, на c++ только лабы и курсовые. Всегда думал, что правильнее использовать NULL, ибо 0 — цифра, NULL — скорее пустота. Даже в небольших лабах это дает повышение читабельности, зачем нужно использовать 0, если используется не c++11?
0 — C++98, C++03. В C++11 тоже можно 0, но лучше nullptr.
То что можно и видимо нужно — я понимаю. Не понимаю почему, ведь 0 — цифра, а NULL — пустота. В целом вопрос — почему нужно использовать 0, когда NULL понятнее?
PS то есть
for(int i=0; i<n; i++){}//  ясно
node *temp = NULL// вот тут явно NULL яснее смотрится чем 0?
Дело в том, что NULL в C++ должен быть определён как
#define NULL 0


Определять NULL как (void *)0 нельзя. Поэтому следующий код является корректным:
f(int x);

void g() {
  f(NULL);
}


Я согласен что это неинтуитивно. Что именно использовать в C++03 — NULL или 0 — определяется стилем кодирования в каждом проекте, так как саму проблему с null pointer constant в C++03 не решает.
А почему код должен быть не корректен? Срабатывает приведение типов — NULL -> int(0).
Просто всегда бесило в коде знакомых студентов
Foo *a = new Foo();
// код код
if(a == 0)

Ну как указатель можно сравнивать с нулем? Это же логически не верно, пусть даже NULL на самом деле — 0.
> Срабатывает приведение типов — NULL -> int(0).

Нет такого неявного приведения типов в C++: T* не приводится неявно к int.

void f(int x);

void g() {
  int *p = 0;
  f(p); // compile error
}
В примере выше ваша функция принимала переменную, а не указатель. Мое, если можно так сказать возмущение как раз и было в том, что для чисел — использовать числа, а для указания пустоты — строки, или указателя, или возможно еще чего-то о чем я не знаю — NULL
Вы, кажется, не предвидели всей полноты последствий:
$ cat zz.cc
#include <iostream>
#include <stddef.h>

void f(int *x) { std::cout << "int *x" << std::endl; }
void f(int x)  { std::cout << "int x" << std::endl; }

int main() {
  f(0);
}


Overload resolution выбирает f(int) для NULL (если #define NULL 0), точно также как и для 0.
Конечно выбирает, ведь 0 === (int)0, а NULL = либо 0, а может nullptr
Если изменить f(0) на f(NULL) — компиляторы не сообразят какую из функций выбрать.
Это потому что в компиляторах именно из-за этого примера есть костыль, который не совместим с C++03. В C++03 нужно
#define NULL 0


Однако в GCC и Clang:
#define NULL __null


И дальше компилятор разбирается по правилам, которых нет в языке.
А как же тогда работает код у ваших студентов? Дело в том, что integral constant expression, равный нулю, является null pointer constant и может быть преобразован к T*.

void f(int *x);

void g() {
  f(2 * 21 - 42); // OK
}
А что там должно не работать? Собственно ваш пример должен бы работать, в функцию же отправляется результат выражения, или я чего-то не понимаю?
А используется такое в переборе указателей в списках, деревьях и тд
То есть вы считаете нормальным, что f(2*24 — 42) компилируется, а f(2*42-43) — нет?
Конечно нет! И потому не согласен с тем, что NULL можно заменять в коде 0.
Что и как реализовано в языке в действительности — один вопрос. Области видимости в классах — важен только на этапе компиляции, однако позволяет не натворить глупостей.
И еще раз — потому надо разделять NULL и 0.
> Конечно нет! И потому не согласен с тем, что NULL можно заменять в коде 0.

Но NULL проблемы не решает, а словить пару часов интереснейшей отладки overload resolution можно элементарно. Если же там был бы написан ноль, было бы всё понятно.

И вообще, это сегодня неактуально. nullptr.
Да, пример использования в студию.
Кода других студентов под рукой нет — вот мое бинарное дерево. Заменив NULL на 0 простой заменой по файлу вроде никаких изменений нет, кроме утечек памяти, которые судя по всему и так должны быть. Надо бы исправить.
Скрытый текст
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

class Product{
private:

    int num;
    // string name;
    // string supplier;
    // int count;
    // string date;

public:

    void input(){
        // cout<<"Product:            "; getline(cin, name);
        // cout<<"Supplier:           "; getline(cin, supplier);
        // cout<<"Date:               "; getline(cin, date);
        // cout<<"Amount:             "; cin>>count;
        cout<<"Number of delivery: "; cin>>num;
    }
    static Product getkey(){
        Product key;
        cout<<"Key: ";
        cin>>key.num;
        return key;
    }
    void print(){
        cout<<"Number of delivery: "<<num<<"\n";
        // cout<<"Product:            "<<name<<"\n";
        // cout<<"Supplier:           "<<supplier<<"\n";
        // cout<<"Amount:             "<<count<<"\n";
        // cout<<"Date:               "<<date<<"\n";
    }
    void output(ostream &stream){
        stream<<num;
    }
    friend bool operator <(Product const a, Product const b){
        return a.num < b.num;
    }

    friend bool operator ==(Product const a, Product const b){
        return a.num == b.num;
    }
};

class node
{
public:
    Product data;
    node *left,
         *right;

    void output(ostream &stream){
        if(this != NULL){
            left->output(stream);
            data.output(stream);
            stream<<" ";
            right->output(stream);
        }
    }
    node(Product var){
        data = var;
        left = right = NULL;
    }
    void remove(){
        if(this != NULL){
            left->remove();
            right->remove();
            delete left;
            delete right;
        }
    }
    ~node(){
        this->remove();
    }
    void output_sheets(){
        if(this != NULL){
            if(left == NULL && right == NULL){
                data.print();
            }else{
                left->output_sheets();
                right->output_sheets();
            }
        }
    }
};

class bintree
{
private:
    node *root;
public:
    bintree(){
        root = NULL;
    }
    bool add(){
        Product new_data;
        new_data.input();

        if(root == NULL){
            root = new node(new_data);
            return true;
        }
        node *elem = root,
                        *parent = NULL;
        bool isLeft;
        while(elem != NULL){
            if(new_data == elem->data){
                return false;
            }
            isLeft = new_data < elem->data;
            parent = elem;
            if(isLeft){// если новый элемент меньше корня
                elem = elem->left;
            }else{
                elem = elem->right;
            }
        }
        if(isLeft){
            parent->left = new node(new_data);
        }else{
            parent->right = new node(new_data);
        }
        return true;
    }

    void output(ostream &stream){
        root->output(stream);
    }
    void remove_max(){
        node *elem = root,
             *prev = NULL;
        while(elem->right != NULL){
            prev = elem;
            elem = elem->right;
        }
        if(elem == root){
            root = root->left;
        }else{
            prev->right = NULL;
        }
        delete elem;
    }

    void output_sheets(){
        root->output_sheets();
    }

    Product find(){
        Product key = Product::getkey();
        node *elem = root;
        while(elem != NULL){
            if(key == elem->data){
                return elem->data;
            }else if(key < elem->data){// если новый элемент меньше корня
                elem = elem->left;
            }else{
                elem = elem->right;
            }
        }
        return Product();
    }
};

void clear(){
    cout<<"\n";
    for(int i=0; i<30; i++) cout<<"-";
    cout<<"\n";
}
void menu(){
    clear();
    cout<<"1) Add node\n";
    cout<<"2) Find node by key\n";
    cout<<"3) Output tree\n";
    cout<<"4) Remove node with max key\n";
    cout<<"5) Output tree's sheets\n";
    cout<<"6) Output in file\n";
    cout<<"0) Quit\n";
    cout<<"Your choise: ";
}
int main()
{
    bintree tree;
    int ch;
    while(true)
    {
        menu();
        cin>>ch;
        switch(ch)
        {
            case 1:
                if(!tree.add()){
                    cout<<"Element with this key exsist";
                }else{
                    cout<<"Element succesfully added";
                }
                break;
            case 2:
                tree.find().print();
                break;
            case 3:
                tree.output(cout);
                break;
            case 4:
                tree.remove_max();
                break;
            case 5:
                tree.output_sheets();
                break;
            case 6:{
                char filename[50];
                cout<<"Filename: ";
                cin>>filename;
                ofstream filestr(filename);
                if (filestr.is_open()){
                    tree.output(filestr);
                    filestr.close();
                }else{
                    cout << "Error opening file";
                }
            }
            break;
            case 0:
                return 0;
                break;
        }
    }

}

Я не вижу где вы здесь использовали нетривиальный integral constant expression, равный нулю, в качестве null pointer constant.
Потому что я, что возможно в корне не верно — не стал перебирать весь список за добавлением нового элемента, а храню в объекте ссылки на последний и первый элемент, мне это не требуется. Реализация именно в функции добавления, что-то вроде
node *temp = root,
     *prev = 0;
while(temp!=0){
    prev = temp;
    temp = temp->next;
}
prev->next = new node();
И? Вы же написали temp!=0, а не temp!=1-1.
Да, я нигде не писал что кто-то придумывает выражения для проверок. Я написал что сравнение указателя и числа выглядит странно и не логично, как и присваивание.
Так, стоп. Было сказано следующее:

gribozavr:
> f(2 * 21 — 42); // OK

На что вы ответили:
> А используется такое в переборе указателей в списках, деревьях и тд
Мы с вами видимо запутались, это был ответ на это:
gribozavr:
> А как же тогда работает код у ваших студентов?
PS Хотя судя по всему я просто не совсем правильно ответил, но относилось именно к этому.
> if(this != NULL){

Epic. Так не бывает.
Я долго сомневался прежде чем так писать. Однако лабу приняли, все ок. То есть компилятор сам не отправит пустой объект в функцию?
При чём тут компилятор? Вызывать функции-члены если у вас нет живого объекта — undefined behavior в момент вызова.
Вообщето такое возможно но мало вероятно.
SomeClass *lv_Obj = 0; lv_Obj->DoSmth(); // функция вызовется и если внутри нет обращения к полям то ещё и успешно выполнится.

но вообще так писать разуметься не хорошо, а если уж и пишешь то следует писать
if (!this) return;

ибо ненужные скоупы это тоже плохо.
даже не поморщится
liveworkspace.org/code/XjE1O$0
Вызвать можно, но это вызовет ошибку в случае если есть обращение к полям, виртуальным методам, или если сам метод является виртуальным (последние не совсем так но проблемы могут возникнуть).
Разыменование нулевого указателя — неопределенное поведение. А то, что оно не вызвало ошибку — это один из вариантов этого поведения исключительно по воскресеньям.
while (1) { // НЕТ!
  :
}

Вот, кстати пример из статьи. Все знают, что while работает только с булевыми значениями, а значит все остальное внутри скобок приводится к булевому значению. Также все знают, что 1 == true.
Так почему же зная то, что NULL = 0 — стоит писать 0, а в циклах такая запись не естественна?
См. ответ выше. Проблема в неинтуитивном определении null pointer constant в C++03.
В С++ нет bool это иллюзия есть лишь 0 и не 0 так что там ничего не приводится.
Так почему же зная то, что NULL = 0 — стоит писать 0

как минимум потому что NULL является макросом который можно переопределить.
а в циклах такая запись не естественна?

с циклами я кстати с автором тоже несогласен while (1) гораздо лучше
#define true 0 // happy debugging motherfucker!
И? Области видимости тоже можно переопределить, только зачем?
Что и? проблема в том что это можно сделать, и это может повлечь не очевидную проблему (злая шутка)
Поэтому писать 0 всё же лучше, а с точки зрения читаемости это ничего не поменяет.
Ты о чём? писать 0 вместо NULL это UB? мда…
даже не поморщится
http://liveworkspace.org/code/XjE1O$0
Вызвать можно, но это вызовет ошибку в случае если есть обращение к полям, виртуальным методам, или если сам метод является виртуальным (последние не совсем так но проблемы могут возникнуть).
Вы не знаете что такое UB, а спорите.
в данном случае это не неопределённое поведение, и если бы вы знали как устроены методы в языке С++ вы бы не говорили этой глупости.
Методы в языке С++ никак не устроены. Внутреннее устройство методов не регламинтируется стандартом. Зато стандартом оговаривается, что разыменование 0-го указателя — UB.
Да про раз именовывание я как-то забыл, действительно, прошу прощения. Просто зациклился на том что метод это функция со скрытым параметром this
Ответил до редактирования.
Нельзя делать #define NULL whatever в пользовательском коде.

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

я не говорил что это не UB, несомненно это UB но это не значит что этого нельзя сделать
http://liveworkspace.org/code/XjE1O$2
И какой в этом смысл? Ввести UB в свою программу для лулзов?
Ну в универе мы так часто прикалывались друг над другом, от туда и появилась устойчивая привычка использовать 0 вместо NULL.
Это действительно убедительная мотивация пункта рекомендаций стиля программирования от профессионала, не поспоришь.
Ну хз я стараюсь предвидеть любые ситуации даже такие незначительные, а что писать 0 вместо NULL это плохо? Честно говоря везде где я работал писали 0, и никаких проблем никогда не было. Может у вас есть история получше?
Это плохая мотивация, независимо от того, какой вариант на самом деле лучше. А вдруг кто-то (совершенно без UB) сделает #define print zzz, а у вас куча функций так называется? От этого тоже будете «защищаться»?

Моё мнение выражено выше. NULL или 0 — разницы нет, так как *проблему* это не решает.

habrahabr.ru/post/172091/#comment_6046361
Нельзя делать #define NULL whatever в пользовательском коде.
Можно, другое дело что это очень плохо, но тем не менее сделать это можно.

Технически, это возможно, вы правы. Компиллятор даже возможно скомпиллирует вашу программу. Но никто не сможет предсказать, как она будет рабоать. Потому что, используяю такой define, вы нарушили правила игры. Теперь ваша программа — уже не валидная программа на языке С++.
Вопрос был из читабельности. 0 — число. NULL — пустота, для чисел является нулем.
Если кто-то может зло пошутить — это не повод писать не понятно.
А кому непонятно? Новичкам разве что, но гадлайны, делаются для разработок в компаниях куда не берут людей которые не знают языка. Обычно за
if(ptr != 0) по рукам бьют т.к. это равносильно if(SomeBool == true) конечно компилятор это разрулит, но обычно это заставляет обращать лишнее внимание на условие.
if(SomeBool == true) — подобное, как мне кажется, надо использовать только в языках с нестрогой типизацией и не ==, а ===, но это другая тема и тут я с вами согласен.
А за foo *bar = 0 (представим что у нас еще нет nullptr) по рукам не бьют?
А за foo *bar = 0 (представим что у нас еще нет nullptr) по рукам не бьют?
Нет в С++ это корректное выражение которое понимает каждый.
Эх… Вот вырасту и буду бить. По мне — так это не логично. Уже 25 раз описал почему.
operator bool bool() const значит не существует?
Вопрос был «зачем использовать 0?»

Где то читал, зачем и почему, но забылось уже, так что присоединюсь к вопросу.
0 — присутствие отсутствия, NULL — отсутствие присутствия ©
UFO just landed and posted this here
Если это не указано явно, C++ считает, что возвращаемое значение имеет тип int.

А мне казалось, он как раз это запрещает (если не крутить некоторые флаги, по крайней мере).

51. Символ указателя или ссылки в языке C++ следует ставить сразу после имени типа, а не с именем переменной.

Мне такой способ тоже больше нравится, но он имеет одну проблему.
int* ptr, not_ptr;
int *ptr, not_ptr;

Второй вариант более очевидный. Хотя оба плохие. :)
Угу. Мне тоже куда больше нравится, когда звёздочка в объявлении указателя «клеится» к типу, нежели к имени переменной.
Однако, при этом я следую ещё одному правилу: каждая инициализация должна требовать отдельной строки для себя.
Странно, что в статье про это ничего не говорится (если только я это пропустил).

А в целом, статья хорошая.
Хоть и не со всеми пунктами согласен — но лучше кодить по жёстко утверждённому стиль, чем вообще без оного.
Второй вариант однозначно более очевиден, особенно, когда объявляются несколько переменных через запятую.
Глядя на первый вариант может показаться, что объявлено два указателя на переменные целого типа. Хотя с точки зрения компилятора C++ в обоих случаях указателем является только ptr, а not_ptr просто переменная целого типа.
Если объявляем много указателей, например:

Stream *Stream1, *Stream2, *Stream3;

то второй вариант не просто предпочтительнее, а единственно логичный.

А в таком примере уже не работает правило 12:

void setStream(Stream *Stream1, Stream *Stream2, Stream *Stream3);
Рекоммендация #0: используйте любой, подходящий вам и команде стиль, но в рамках одного проекта других стилей быть не должно и придерживаться его надо обязательно.
Ещё хочу написать кое-что.

1. Любой символ, заданный в тексте программы своим кодом, задокументировать в свободной форме.

s += L'\u2212'     // 2212 = minus sign

2. Специфично для Delphi/Builder. Любая ссылка на конкретный объект VCL, в тексте, в форме или в виде уникального события — дай ему говорящее имя. Для «массовых» событий, общих для нескольких компонентов с определением компонента по dynamic_cast<TButton*>(Sender)->Tag, это не требуется, однако само событие должно иметь говорящее имя.

3. Если у метода есть аналог из известной библиотеки, подстраиваться под неё. Например: const char* Printf::c_str(), но не cStr().
Далее.

4. Венгерская запись — не именование типа (в IDE уже 15 лет есть навигация по коду), а сокращение идентификаторов. Например, EPreprocess = PreprocessException, nUsers = UserCount.

5. Различать id (суррогатный код), index (порядковый номер), count (кол-во объектов, которые компьютер различает), quantity (кол-во объектов, которые компьютер не различает) и number (что либо из реальной жизни, так называющееся). Например: carId — идентификационный код, отличающий машину от других, carIndex/iCar — номер машины из n шт., carCount/nCars — кол-во машин в гонке, carQty — кол-во машин в продаже, carNo — номер машины (государственный или стартовый).
Не без замечаний, но в целом годно.
К п. 41: Опыт подсказывает, что в .cpp первым инклюдом удобно включать собственный .h-файл. Этим проверяется, что он самодостаточен, т. е. не зависим от предыдущих инклюдов в .cpp.
С этим согласен, сам постоянно так делаю.
Гораздо хуже, когда твоя библиотека в другом проекте будет требовать библиотеки, которые не подключены.
Много уже неактуального, например про 80 колонок или символы табуляции, не говоря уже об игнорировании актуального C++11.

Есть вредные советы. Из того, что еще не упоминались, например 8. Следует называть имена типов в шаблонах одной заглавной буквой. А кто угадает, что означает каждый аргумент:
template<
    class K,
    class T,
    class H,
    class E,
    class A
> class unordered_map;


А вот так это выглядит в оригинале:
template<
    class Key,
    class T,
    class Hash,
    class KeyEqual,
    class Allocator
> class unordered_map;



Отдельная история с именованием переменных. Предложенная здесь система похожа на использующуюся, например, в Qt. Microsoft используют немного другугю системму наименований, а вот standard C++ Library и boost вообще использует большинство имен в нижнем регистре с подчеркиванием. Тут может быть только один совет — именуйте так, как принто в той библиотеке, которую вы используете больше всего.

И      еще,   я            не       могу 
понять почему выравнивание улучшает читаемость
Если в проекте все строчки не длиннее 80 (или 100) колонок, удобно открывать 2 файла параллельно.

С++ похоже один из не многих языков, где так и не смогли окончательно решить, как писать переменные :)

Ваш пример выравнивания, конечно, читать неудобно. Чаще оно используется немного по-другому. Мне кажется, есть 2 типа людей: 1) те, кому субъективно нравится выравнивание, они часто его используют; 2) остальные выравнивание практически или совсем не используют. Все при этом руководствуются только улучшением читаемости.
Я сам стараюсь не вылазить за сотню, но 80 — все-таки уже перебор.

А вот на счет выравнивания, тот пример, который приведен в статье, мне кажется не многим лучше. Ну да, я вижу паттерн, но у меня от него только в глазах рябит. Читаю же я все равно слева направо, и что там в других строках — мало интересует. В типографике наоборт, стараются избавляться от коридоров.
Текст на естественном языке читается по-порядку, всплошную, в то время как в исходном тексте структура имеет огромное значение.
Вообще не понимаю, кому в голову может придти использовать выравнивание в типичном проекте, как показывает практика выравнивание производится один раз, при написании кода, а со временем все это превращается в фарш после переименований классов и никем не поддерживается. А при каждом переименовании просматривать и переформатировать n кусков кода — слишком дорогое удовольствие. Правда автоматическое выравнивание инструментальными средствами наверное решает проблему, но сталкиваться не приходилось.
100-120 тоже нормально открываются на современных FullHD и больше мониторах. А то писать из-за 80 символов постоянно вот так:
for (int tableNo = 0; tableNo < nTables;
     tableNo += tableStep) {
  ...
}

не очень удобно.
Если в коде много строк, которые не влезают 80 символов, возможно с кодом что-то не так. Поясните, к примеру, чем оправдано использование название tableNo вместо обычного i (j, k и т.д)?
Я скопировал это из статьи, совет 39. Без понятия, откуда у автора этот код.

Например,
animState->setTimePosition(std::min(anim->getLength(), animState->getTimePosition())); 

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

animState->setTimePosition(
    std::min(
        anim->getLength(),
        animState->getTimePosition()
    )
);

Это стандартный капитанский совет людей, кто все вмещает в 80 символов. Только вот от этого читать ее нифига не стало лучше, а как по мне — наоборот.
И это функция с одним параметром.

Потом, самое неприятное — это переносить явно однострочные функции.
deltaTransform.translate = srcBone->getInitialPosition() - dstBone->getInitialPosition();
deltaTransform.rotate = dstBone->getInitialOrientation().Inverse() * srcBone->getInitialOrientation();
deltaTransform.scale = srcBone->getInitialScale() / dstBone->getInitialScale();


Замечу, что я не выступаю против переносов. Естественно, когда строка не вмещается в разумных границах монитора, программиста хочется ругать. Дело просто в том, что 80 строк правда далеко не всегда хватает, а городить перенос на каждом чихе — бесит. 100-120 столбцов — самое то. Лично у меня стоит ограничение на 110 символов, я спокойно отрываю два файла на одном мониторе FullHD.
Читать стало лучше, потому что глаз видит больше. Газетные колонки по этой причине существуют. Еще в программировании опасны паттерны, потому что глаз замыливается и можно перепутать X с Y, например.
Еще это удобнее читать в редакторе, в котором открыто 6 вкладок, по ssh, на телефоне и нетбуке, на гитхабе. 80 символов — самое то.
Видите, Вам лучше, а мне нет. Очень все индивидуально.
Соглашусь, что в крупных открытых проектах лучше делать все по 80 символов, ибо мы не знаем, кто этот проект будет читать. Внутрикорпоративно же я приблизительно знаю, каким оборудованием владеет народ, и делать такие никому ненужные ограничения смысла не вижу.
Я бы не сказал, что все индивидуально. Поле зрения у глаза круглое, поэтому длинные строки нужно читать, а «квадрат» можно охватить взглядом сразу. Хотя насчет 80 я погорячился, пожалуй.
При беглом анализе — да, скорее, Вы правы.
А вот если нужно вчитываться в написанное, то при сильно скомканном коде (как в примере выше), приходится включать мозговой парсер. Строка же принимается также, как строка в обычной книжке.
И да, если приспичить сделать все по феншую, для VS есть плагин StyleManager, который можно настроить на любой стиль и длину строк. Есть и свободные аналоги, но названия не помню.
Вот по поводу последнего — вы погорячились.

Видите вы строчку внутри вложенного цикла:
var tratata = MumbleThis(i, j);

Неужели она лучше чем:
var tratata = MumbleThis(year, dayOfYear);


Индексы i,j — допустимы только когда 100% кристально понятно, что они обозначают и невозможно разночтений или необходимости вникать.
Я бы сказал, что они допустимы только когда это абстрактные индексы, не имеющие воплощения в предметной области (или имеющие именно в таком виде, например в математической формуле именно i используется). Потому что-то, что кристально понятно сейчас, пока тело цикла из одной строки состоит, потом может уже не быть понятным вообще, особенно если десяток строк добавить. В эту же степь неявные преобразования «физических» номеров (начинаются с 1) в «абстрактные» индексы (с 0) типа a[month-1] = 0; Сиди и гадай потом, то ли это преобразование в индекс, то ли какая-то логика про предшествующий месяц.
Соглашусь, верное замечание.
Крайне неприятно, когда в исходном коде попадаются строки по 150-200 колонок, а то и больше, особенно когда кроме IDE открыто ещё что-нибудь, и нужно уменьшить ширину окна, или когда код просматриваешь не в IDE, а в текстовом редакторе.
Что касается выравнивания, читаемость оно улучшает, но в довольно специфических случаях: при задании значений двумерным массивам, например.
Выравнивание улучшает читаемость при использовании случае выравнивания однотипных (в данном контексте) сущностей. Прекращение выравнивания означает, что пошли сущности другого типа.
Не проще ли для таких случаев использовать разделитель в виде пустой строки?
Обычно он используется для разделения логических блоков. Могут быть неоднозначности.
Вкратце: всё пишем будто на джаве.
А кто такие эти geosoft.no чтобы давать рекомендации по стилю C++? У них рекомендаций больше, чем написанных программ. Я вижу у них на сайте только ряд java-библиотек. Как объяснить программистам, что им теперь нужно писать по указке geosoft?

Есть куча нормальных стандартов, написанных реальными разработчиками, а не Wannabe Styleguide Writer-ами.
Google C++ Style Guide
Mozilla Coding Style
Linux kernel coding style
Microsoft Design Guidelines for Class Library Developers
Boost Guidelines
Alexandrescu C++ Coding Standards
UFO just landed and posted this here
UFO just landed and posted this here
Это все так, но добавление подчеркивания к имени членов класса позволяет легко отличить их от локальных переменных и позволяет избежать конфликтов имен. А кодирование типа данных в имени это действительно архаизм времен убогих IDE
48 Про глобальные функции не согласен.
49 Не согласен, что всегда все члены класса следует объявлять как private и писать к ним get/set. Чаще — да, всегда — нет.
58 Да ладно?
70 Следовало бы и про nullptr сказать.
51, 72-83 холиварные пункты. Считаю, лучше было бы их убрать, либо написать более демократично.
Добавил в избранное, хотя тут годных советов максимум процентов 30-40. Остальные — спорные, холиварные, вкус фломастера (на вкус и цвет фломастеры разные).
В статье больше спорных советов, чем полезных. В дополнение к критике выше:

Все определения должны находиться в файлах исходного кода.

class MyClass
{
public:
  int getValue () {return value_;}  // НЕЛЬЗЯ!


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

llvm.org/devmtg/2012-11/Carruth-OptimizingAbstractions.pdf
llvm.org/devmtg/2012-11/videos/Carruth-OptimizingAbstractions.mp4
Все это становится полезным только для 20% кода, работающего под нагрузкой. Если же ваш геттер вызывается 3 с половиной раза в сутки, как это чаще всего и бывает, то никакого улучшения производительности вы не заметите. Зато заметите, что
— теперь вам нужно перекомпилировать все зависимые модули при изменении одного геттера.
— не может идти речь ни о какой бинарной совместимости. Вы просто не можете взять и поменять реализацию, не поменяв интерфейс.
Если вы пишете типичное энтерпрайз GUI приложение, пожирающее мегатонны RAM, в которое меланхоличный пользователь раз в минуту тыкает мышкой и затем размышляет «а что это оно мне написало», то ваш подход оправдан. Я работаю в несколько другой области.
Вы знаете, я просто смотрел код под профиллировщиком и видел, что тормозит. Всего один раз, после того как я уже устранил 3 или 4 основных затыка, у меня производительность уперлась в цепочку из 5 последовательных вызова виртуальных функций.
Вот вам простой эксперимент: я скомпилировал Clang в обычном режиме Release+Asserts, и в таком же режиме, но добавив -fno-inline -fno-inline-functions. Измерялось время исполнения тестов (CPU time).

R+A: 12m40.520s
R+A no-inline: 22m29.980s

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

КО: заголовочный файл становится частью многих единиц трансляции.

> С другой стороны, ваш тест не показывает, сколько процентов кода тормозит.

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

Без инлайнинга код начинает тормозить везде равномерно.

Ну вот не факт. Какие-то части кода вызываются чаще, другие — реже. Мне вообще кажется, что основные проблемы были вызваны тем, что перестало срабатывать встраивание в каких-нибудь контейнерах. Или вроде того случая, который вы привели по ссылке в начале обсуждения.
> Да, но вы отключили встраивание глобально

Да. Что вы дальше хотели сказать я так и не понял. (Обычный R+A компилировался без LTO, если вы это имели ввиду.)

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

Основные — да. Но не все. Кроме того, мы же обсуждаем «правило» что инлайн функции в классах определять «НЕЛЬЗЯ». Поэтому считаю тест корректным.
Понимаете, вы выключили слишком много inline-ов. При простом переносе определения геттеров в файлы имплементации, выключится существенно меньше inline-ов. Поэтому тест не совсем корректен.

Кроме того, мы же обсуждаем «правило» что инлайн функции в классах определять «НЕЛЬЗЯ»

С оговоркой, что если очень хочется, то можно.

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

Не согласен со словом «существенно». Зависит от приложения.

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

А вот с этим я согласен.
Ага, особенно для шаблонов. Впрочем, если в clang осилят нормальные модули, то быть может вообще получится уйти от разделения на h и cpp просто правильно подставляя экспорты и импорты.
Модули не подразумевают что у вас не будет .h. Они останутся. Модули говорят что у вас не будет #include. В Clang Douglas Gregor идёт дальше и предлагает оставить и #include, но автоматически преобразовывать его в импорт, если .h модульный.

clang.llvm.org/docs/Modules.html
34, Первый раз слышу про файлы с расширением *.с++ (MyClass.c++), это где так принято именовать?
Правила явно писал Java кодер, некоторые вещи слишком избыточные, POD структуры вполне себе удобная сущность, да и в приватных классах можно не городить весь огород с сокрытием полей, только читаемость теряется.
Приватные поля лучше записывать через
m_name;  

Так к ним быстрее из IDE добраться. В общем, Qtшный стандарт как-то ближе и теплее.
Удивительно прям, почти нет заминусованных комментариев. Обычно такие холиварные топики похожи на поле битвы.
ИМХО тут очень, очень много вредных советов.
1. Допускаются любые нарушения рекомендаций, если это улучшает читаемость.

Незя, ибо читаемость за частую очень субъективно.
2. Правила могут быть нарушены, если против них есть персональные возражения.

XD тогда зачем вообще заводить гайдлайни по коду?
Позволяет легко отличать переменные от типов, предотвращает потенциальные коллизии имён, например: Line line;

Всегда думал что наилучший подход в данном случае использовать префиксы видимости (хоть они и не очень нравятся), они очень хорошо помогают в условиях современных IDE с интелесенсом, и при чтении, сравни:

  m_Field = lv_Field + _Offset;
/*
сразу при чтении понятно m_Field - поле класса lv_Field1 - локальная переменная метода _Offset - параметр метода.
при этом нет никаких конфликтов имён, быстро читается + при вводе m_ интелесенс сразу выведет поля также и при lv_ и _
/**/
  field = field1 + offset; // что от чего и где совсем непонятно.


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

Зачем? при этом ведь возможен конфликт имён, и из-за этого тебе потом приходится ставить постфикс в приватных переменных, лучше с большой буквы т.к. это в любом случае будет выглядеть лучше
SomeObj.Method() // гармония SomeObj.method() // дизгармония
7. Названия пространств имён следует записывать в нижнем регистре.

Логика тоже неясна. Зачем дробить понятие наименование на такие группы? когда конфликты в случае namespace, class name, func name. практически никогда не происходят, гораздо лучше задать им единый стиль.
SomeNamespace::SomeObj.Method() // гармония some_namespace::SomeObj.method() // дизгармония
8. Следует называть имена типов в шаблонах одной заглавной буквой.
Это самый вредный совет который только можно было придумать, параметры шаблона, равносильны параметрам функции, тыже небудешь делать в функции такие параметры: void SomeFunc(int A, int C, int B, char* T)
9. Аббревиатуры и сокращения в именах должны записываться в нижнем регистре.

Не имеет смысла аббревиатуры на то и аббревиатуры что имеют определённый стиль написания, и менять его не следует.
11. Членам класса с модификатором private следует присваивать суффикс-подчёркивание.

Суффиксы самая бессмысленная вещь, либо префиксы либо без них. Если приватное имя, и имя параметра будут совпадать, то будет очень легко допустить ошибку при наборе + интелесенс поможет сделать ошибку, а при чтении суффикс _ вообще будет незаметен. Вредный совет.
12. Настраиваемым переменным следует давать то же имя, что и у их типа.

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

SomeFunc(string& stringPath, int CountInt, float OffsetFoloat)

Зачем повторять тип когда IDE сама подскажет?
14. Переменные, имеющие большую область видимости, следует называть длинными именами, имеющие небольшую область видимости — короткими.

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

Довольно спорный момент, get/set это так называемые свойства, которые в других языках реализованы обычным именем свойства без get/set, да и без get/set облегчается рефакторинг.
18. Слово compute может быть использовано в методах, вычисляющих что-либо.

Зачем?
Дайте читающему сразу понять, что это времязатратная операция.

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

Аналогично с предыдущем высказыванием.
Следует избегать сокращения init.

Почему? Чем оно вредно?
21. Переменным, представляющим GUI, следует давать суффикс, соответствующий имени типа компонента.

Спорно
23. Префикс n следует использовать для представления числа объектов.

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

Почему? Многие сокращения являются общепризнанными, например Avg, Min, Max, Sin, Cos, Cmd, Init и т.д. зачем перегружать названия и делать их длинными когда в некоторых случаях это не несёт смысла.
37. Содержимое файлов не должно превышать 80 колонок.

Спорно, экраны уже давно не такие маленькие.
38. Нельзя использовать специальные символы (например, TAB) и разрывы страниц.

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

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

все компиляторы уже давно поддерживают #pragma once зачем прикручивать гуарды непонятно.
51. Символ указателя или ссылки в языке C++ следует ставить сразу после имени типа, а не с именем переменной.

Наоборот, т.к. символ указателя или ссылки в языке C++ относится к переменной а не типу.
53. Следует избегать неявного сравнения булевых (логических) переменных и указателей с нулём.

Глупость, в стандарте С++ и С нет особого типа bool который необходим для условий, соответственно не имеет смысла писать

if (nLines != 0) // в С++ это равносильно if (nLines != false)

58. Следует избегать использования break и continue в циклах.
Такие выражения следует использовать только тогда, когда они повышают читаемость.

мда… а в логике работы программ эти выражения не участвуют да?
Если это не указано явно, C++ считает, что возвращаемое значение имеет тип int.

Неужели может попробуешь скомпилировать код с функцией без типа возврата? Такое допускается, только с main и то выдаёт ошибку.
71. Основной отступ следует делать в два пробела.

Табами и только табами, т.к. табы можно настроить в большинстве IDE а пробелы нет.
72. Блоки кода следует оформлять так, как показано в примере 1 (рекомендуется) или в примере 2, но ни в коем случае не так, как показано в примере 3. Оформление функций и классов должно следовать примеру 2.

О да, 1 и 2 являются одним из вечных источников холиваров K&R или Олман в области стиля. И гайдлайн который вроде бы должен как раз такие моменты разруливать включает оба этих момента, круто!
89. Используйте выравнивание везде, где это улучшает читаемость.

ты же говорил что так

if (a == lowValue) compueSomething(); else if (a == mediumValue) computeSomethingElse(); else if (a == highValue) computeSomethingElseYet();

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

В общем очень, очень плохая статья. Многое писал КЭП, много вредных советов, много спорных моментов, много бессмысленных советов, много не освещённых моментов (например макросы, шаблоны, вложенные классы, датахолдеры, употребление explicit, перегрузка операторов, абстрактные классы(они же интерфейсы) ). Очень многое можно выкинуть и упростить.
про SomeObj.Method() и someObj.method(); категорически не согласен. Как тогда отличить SomeClassConstructor() от SomeStaticMethod(); и потом, допустим, должно быть SomeClass::Method(), но при этом someClass.method(); же, что еще больше путаницы создает.
Ничего не понял, в чём проблема? Отличить конструктор от метода? помоему это очевидно и с именем никак не пересекается.
и потом, допустим, должно быть SomeClass::Method(), но при этом someClass.method(); же, что еще больше путаницы создает.

в чём конкретно? что трудно различить SomeClass::Method() и SomeClassObj.Method()? А :: и. не помогут? К тому же я вообще ни разу не встречал ситуации когда статический метод и обычный были с одинаковым именем.
Можно различать их по цвету, современные IDE умеют подсвечивать разным цветом различные элементы (например статические методы — оранжевые, нестатические — лазурные).
Heroes of Might And Magic 3 научили меня, что не оранжевые, а ржавые.
Вот еще бы софтину какую посоветовали, чтобы автоматически за соответствием стандартам следить. У языков вроде питона или руби есть такие инструменты. А для С++ чаще всего больше чем реформаттер отступов и скобок найти очень сложно.
Есть одна хорошая книга Николас А. Солтер, Скотт Дж. Клепер. C++ для профессионалов (или второе издание с включением C++11, многопоточности и прочих интересных тем), которая содержит много полезных советов. В частности, по поводу именования переменных:

gSettings — префикс выражает глобальный статус переменной;
srcName, dstName — различие между объектами (например при копировании);
mName — префикс выражает статус члена данных (или "_" вместо «m», по аналогии с Python).

На странице 187, в данной книге есть таблица с префиксами и их значениями. На мой взгляд, префиксы куда удобнее для понимания и для работы в IDE, а также несут дополнительную информацию о назначении переменной (член данных, временная, статическая и так далее). К слову, именование констант с префиксом «kMaximumLength» выглядит куда лучше, чем «MAXIMUM_LENGTH».

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

Foo::Foo( int arg1, int arg2 )
    : mArg1( arg1 )
    , mArg2( arg2 )
{}
А это уже венгерка с сокращениями. Префикс _ нельзя, он за компиляторозависимыми вещами определен. Лучше уж m_, mMember несколько дико смотрится.
Ну не такая уж и злостная венгерка, как например «rgfpArray») Все вполне пристойно, если подходить без фанатизма.
Тот же QtCreator генерирует имя поля для свойства в стиле «m_», что соответствует венгерской нотации. Ну а в Qt Coding Style ничего не написано по данному вопросу, если мне не изменяет память.
Переменные I и l надо запретить под страхом отката коммита. Причина, надеюсь, очевидна.
Я лично не понимаю на что расчитаны все соглашения насчет именования переменных, классов, функций, констант. Достаточно одной хорошей практики: «делайте методы короткими». И по барабану как что именуется.

P.S. некоторые предложеные префиксы/суффиксы вообще идут в разрез с некоторыми другими пунктами. Например nLines — правильно согласно другому же пункту «number_of_lines»
class UniCorn {
    public:
int get_x() {return X; };

    void setX(int salad) { X = salad; };

    int Y()
    {
        return _y;
    }

    void SetY(int JesusWillBeatTRex) {
        _y = JesusWillBeatTRex;
    }

private:
    int    X;
    int    _y;
};


действительно, главное методы короткие
Во первых я про форматирование ничего не говорил (но это лечится автоидентом многих современных редакторов), а во вторых в таком тяжелом случае (я имеею ввиду автора такого кода) не помогут даже эти правила :). В одном случае будет называть переменную (согласно правилам) nLines, в другом случае — numberOfStrings. В одном случае функция будет членом и будет с префиксом m_, в другом будет без префикса m_ и тд и тп.

А, так, благодаря коротким методам, этот код читается легко и непринуждённо, хоть он и плохо форматирован.
Не совсем понял, чем не угодило сокращение init (да и cmd, если уж на то пошло).

Объяснение про while(1) тоже не уловил — «Проверка на единицу не является необходимой и бессмысленна.» — это что означает вообще?
Тут скорее всего имелась ввиду формальная логика: для человеческого восприятия условие «пока да» — у любого интерпретируется как «всегда да», а вот условие «пока 1» — немного сносит крышу.
Это еще что… Вот 1С куда более мозг сворачивает, например, «Для Каждого Стр Из Товары Цикл»
Словно формальная логика программиста имеет что-то общее с человеческим восприятием :)
Может заставить искать глубокий смысл в единице. Почему не двойка? Почему не минус единица и т. п.
Чтобы увидеть отсутствие глубокого смысла, вполне достаточно знать, как целочисленные типы приводятся к булевскому.
Это хорошо, если программируешь на C++ и только на нём. Но ведь не во всех языках это работает именно так. И когда используешь true и false, не приходится думать: а как же оно там приводится?
Зачем лишние сущности?
mainWindow, propertiesDialog, widthScale, loginText,
leftScrollbar, mainForm, fileMenu, minLabel, exitButton, yesToggle и т. д.

А почему не так, как все привыкли?
wndMain, dlgProperties, txtLogin,
scrlLeft, frmMain, mnuFile, lblMin, btnExit и т. д.
LPCWSTR, HRSRC, LGRPID, КРОВЬ, КИШКИ, WINAPI!
Я совсем о другом — не об использовании сокращений, а о большем акценте на назначении контрола, чем на его типе.
Тип ведь в программе и так записан.
propertiesDialog — как раз и есть акцент на назначении. Начинаем набирать назначение, а остальное завершает автокомплит.
dlgProperties — акцент на типе.
Странно, а почему в правиле 85 мы советуем использовать пробелы перед скобками, а в 86 их не используем.
Прочитал статью, и немного порадовался, что я почти всё делаю правильно.
И всё-таки мне понятнее такая конструкция:
while (true)
{
   sendPacket("test");
   checkInput();
}


Нежели когда скобка стоит сразу после (true). Так логичнее, сначала цикл/условие, затем тело.
Вот тут немного подробней про стиль расстановки скобок: тот, что Вам не нравится, называется K&R. Не думаю, что это принципиально; всё зависит от тех соглашений, которых Вы придерживаетесь.
Мне тоже так удобней. По сути своей скобки — это ограничители, логические, функциональные, области видимости… и в коде вполне могут быть конструкции подобные приведённой ниже:
int y = 0;
{
   int x = 0;
   ...
}

Наглядный пример получается при использовании элементов синхронизации:
int zzz;
{
   Lock l(cs);
   ...
}
С другой стороны, скобка в строке с ключевым словом показывает на неразрывную связь блока с этим словом.
И существенно экономит место, прилично увеличивая количество полезной информации, помещающейся на экране.
Годный вброс. Очень много спорных пунктов, не смотря на аргументацию (кстати местами довольно хилую).

К примеру, на мой взгляд, строчное написание с подчеркиванием в качестве отступов читается_лучше, чем верблюжийШрифт.
(Не надо холивара на эту тему, пожалуйста).

Кстати, принятый в GNU формат отступов, тут назван неприемлемым.
void
some_function()
  {
    do_some_stuff();
  }
+1 за строчное написание. CamelCase читается хуже, особенно, когда используются longNamesTalkingForThemselves.
Если открыть документ GNU Coding Style, половина там написанного тут попадает в список нерекомендуемого к использованию.
А если открыть книжку Best Practices, то там и вовсе сказано, что главное — чтобы стиль был единым в пределах хотя бы файла или лучше модуля, и хороший программист без особого труда сможет переключаться между различными стилями.

Так что приведенные рекомендации — всего лишь best practices + coding style одного конкретного проекта/конторы/разработчика. У текста одно достоинство: основные правила формализованы и подробно описаны.
Это отдельный труд, мы в своем отделе уже месяца 2 собираемся формальный документ написать, все никак не соберемся — на code review полагаемся.
Знаю, сам как-то для мелкой команды пытался составить.

Всё учесть не реально, к примеру во всей статье и в комментариях нет ни одного упоминания тернарного оператора. Хотя обычно тот, кто составляет такой документ прямо его зарещает (еще одна тема для холивара — не редко он читается легче, чем конструкция if/else).

Читаемость кода — очень субъективная штука. Если мне доводится писать подобные рекомендации, то я пишу примерно следующее:
  • K&R стиль для скобок;
  • строчное написание везде, кроме имен типов, там — CamelCase;
  • #pragma once;
  • отступ — 4 пробела;
  • строго 80 колонок;
  • только блочные комментарии;
  • длинные самодокументирующиеся имена;
  • никакой венгерской нотации;
  • метки — на уровень левее кода, case, private, protected, public приравниваются к меткам;
  • использование auto и decltype;
  • использование static_assert;
  • там, где можно — циклы по коллекции, вместо итераторов;
  • обязательное использование const в параметрах методов;
  • использование final, override;
  • использование constexpr;
  • явное указание всех исключений, которые может выбросить метод.

Остальное — на собственное усмотрение. Уже в перечисленном обычно находится пяток тем для споров, из которых уже выводятся окончательные требования к стилю в проекте.
обычно находится пяток тем для споров
не удивительно:
строчное написание везде, кроме имен типов, там — CamelCase

It's so pythonic ._.______

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

IMHO, очень спорно, ещё Майерс показал, что от throw деклараций вреда больше чем пользы. Даже вменяемые жависты люто ненавидят throws.

там, где можно — циклы по коллекции, вместо итераторов

Почему всё-таки не algorithm?
О нет… похоже, мы начинаем холивар.

Одна из причин, по которой не люблю python (ой сейчас я огребу), именно _.__.___.
Тут надо знать меру, IMHO больше одного символа "_" подряд — недопустимо.

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

А по поводу algorithm:
for(auto item : vect) {
    /* do some stuff */
}

Читается легче, чем
std::for_each(vect.begin(), vect.end(), [] (some_type item) -> void {
    /* do some stuff */
});

или
struct some_functor {
    void operator() (some_type item) {
        /* do some stuff */
    }
} my_functor;

/* ... */

std::for_each(vect.begin(), vect.end(), my_functor);


Давайте закроем тему, все равно в итоге каждый останется при своем мнении.
_.__.___

О, на вашего питона кто-то наступил. Я не намекал на магические методы, с ними как раз всё ок.

for(auto item : vect)

К сожалению, пока не могу использовать новый стандарт. Почти всегда ваш вариант наглядней. Но если нужно просто вызвать метод, я бы всё же написал std::for_each(v.begin(), v.end(), std::mem_fun(&cls::func));. Хотя, конечно, добавить ещё что-то будет сложнее.

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

Можно забыть, когда его пишешь, и потом неожиданно попадёшь не в обработчик catch(std::exception) или catch (std::my_subsystem_exception), а в unexpected(), что довольно неприятно. Жить так, конечно, можно (если всегда оборачивать всё подряд в try {} catch (...) { throw my_exception(); }), но параною развивает. Дело ваше.

Не холиварю, просто интересны мотивы некоторых решений.
А кто-нибудь знакет обоснование, почему в stl и boost нарушаются описанные выше правила именования классов? А также почему включаемые файлы stl не имеют расширения ".h"?
Потому что эти «правила» — личное мнение автора.
Странные ребята минусуют… Вот, кстати, что удалось найти по этому поводу: stackoverflow.com/a/13882155/322708
Underscores and lowercase was the style perferred by Bjarne Stroustrup in «The C++ Programming Language».

Я считаю, что мнение автора языка о том, в каком стиле на нем писать, довольно важно. Это в некотором смысле первоисточник. Вот, Страуструп придерживается того же стиля, что в stl (вернее — stl написан в стиле, рекомендованном Страуструпом). Или еще похожий вопрос: как писать ключевые слова в Паскале — маленькими буквами? с большой буквы? большими буквами? Кто во что горазд, так и пишет. А между тем Никлаус Вирт явно дает понять, какой из вариантов он имел в виду — ведь в его последнем «паскалеподобном» языке, Обероне, ключевые слова пишутся только большими буквами; значит, с большой вероятностью так должно было быть и в Паскале.
Чтобы различать достандартный вариант, где все было в глобальном неймспейсе и новый, в котором появился namespace std. Как только старый вариант окончательно стал deprecated, достандартный вариант убрали, хотя он до сих пор встречается в плохих книжках. То есть раньше можно было писать или так:
#include <iostream.h>
int main() {cout << «hello»;}
Или так:
#include int main() {std::cout << «hello»;}
Ну несколько странный вариант делать различия — убирать расширение .h из имен включаемых файлов. Если бы сделали .hpp вместо .h, я бы еще понял. Но совсем убирать…
Ничего не могу с собой поделать, мне очень нравятся stl/boost naming conventions в сочетении с 1TBS. Жалею, что на работе принят более java-подобный стиль именования с отступами в стиле C#.
UFO just landed and posted this here
Очень спорный документ. Я лично вообще против настолько развернутых style-guide-ов, от них холиваров больше, чем пользы. Да и пройдет в лучшем случае месяц, а то и несколько, full-time работы прежде, чем это все хоть как-то запомнится.

И, может, я просто не заметил, но видимо главное правило все-таки упустили: при правке существующего кода использовать принятый в нем code-style.
это тоже спорно — например по кодстайлу можно определить в каком именно месте поправил что то другой программист, да и если ужасный кодстаил предшественника — с какой стати я должен говнокодить как он?
Ну я в первую очередь имел в виду, что делая правки в чужом проекте надо в первую очередь уважать существующий код-стайл. Да и нет ничего хуже, чем читать потом мешанину из табов и пробелов в качестве отступов. Разные правила расстановки фигурных скобок тоже раздражают. Главное — кода есть 2 вида расстановки скобок, вероятность того, что найдется чел, который напишет и третий, повышается.

С различиями в «мелких» пунктах вроде расстановки пробелов внутри скобок/между операторов еще жить можно.

Ну а вообще… код-стайл — это скорее зона ответственности project manager-а/тимлида/техдиректора проекта (в зав-ти от формата разработки). Но если уж выбрали — лучше придерживаться. Лучшее, что можно придумать во избежание бардака — автоматизироватьпроверку на соответствие или даже форматирование в некоторых случаях.

Для определения где что, кто и когда поправил — есть git/mercrurial/svn. Если кодстайл реально ужасен, то проще скрепя сердце, переформатировать/поправить весь код, хотя бы в основных пунктах, предварительно согласовав это все с коллегами и тимлидом.
Я лично вообще против настолько развернутых style-guide-ов, от них холиваров больше, чем пользы.

удобны для составления автоматических правил проверки/форматирования.
Для коллекции не хватает пунктов:
— С какой стороны от типа ставить const при объявлении параметров;
— Стоит ли использовать using namespace std.
"Все определения должны находиться в файлах исходного кода."
Он шутит? А как же оптимизация? Он не знает, чем отличается реализация в заголовке (объявлении)?
Особенно смешно совместно с рекомендацией использовать функции вместо констант.
Сей господин отдаёт себе отчёт, что это приведёт к явному вызову функции при практически каждом использовании константы?
Это может вылиться в замедление работы программы в сотни раз.
Теоретик какой-то. он сам-то пытался так писать? И как, работало?
Да ещё и "Не следует объявлять переменные класса как public."
Нет, он точно клоун.
> использовать функции вместо констант
Все константы действительно глупо заменять функциями, хотя пример с MAX_ITERATIONS выглядит вполне разумно.
А вот заменять все глобальные переменные функциями get/set — вполне здравая идея, ещё Макконнелл об этом писал. Но лучше, конечно, вообще не использовать глобальные переменные.
И выносить их в CPP-файлы? Вы уверены, что хорошо представляете себе, как оно работает?
Я прекрасно понимаю, как оно работает. Можете объявить функции досупа к глобальным переменным в заголовочном файле с ключевым словом inline, если очень хочется. Это просто неплохая, на мой взгляд, практика, я не утверждаю, что надо всегда и везде так делать.
Он пишет «Все определения должны находиться в файлах исходного кода.»
Бред какой-то.
Особого смысла выносить константы в функции в общем случае нет. Если есть смысл — никто не запрещает вынести. А иначе, это какая-то очередная «война против оператора goto».
Разумеется, бред. Хотелось бы посмотреть, как он шаблоны в cpp-файлы вынесет. Явное инстанциирование работает только в очень ограниченном количестве случаев. А без шаблонов писать на довольно c++ грустно.
локально используемые шаблоны на ура и в cppшнике пишутся.
Особенно смешно совместно с рекомендацией использовать функции вместо констант.
Сей господин отдаёт себе отчёт, что это приведёт к явному вызову функции при практически каждом использовании константы?

В новом стандарте С++ для решения этой проблемы есть ключевое слово constexpr:
constexpr int someIntConstant() { return 25; }

Удивительно, что автор про это не вспомнил.
В новом стандарте отменили независимую компиляцию файлов (я правда не знаю)? Никакой constexpr не спасёт, если тело метода определено вне заголовка — при компиляции остальных файлов доступен только заголовок и компилятор просто не знает значения константы. Всё равно будут генерироваться вызовы функции для каждого использования этой константы. Разве что ясно указывается, что значение константы можно кэшировать (для циклов for и тому подобного. Иначе вообще на каждую итерацию будет вызов осуществляться — как это будут делать компиляторы обычного C++).
Давно умеют делать дополнительную оптимизацию после линковки
lto? Это редко используется и требует огромных ресурсов. Как вы себе в общем случае представляете замену вызова константой после компиляции? Это может потребовать полной перекомпиляции. Что тогда вообще делать на этапе компиляции.
А разве constexpr функция не должна быть явно видна вместе со своим содержимым во всех единицах трансляции?
Кажется, что эти рекомендации процентов на 30 состоят из защиты от дурака, которая от сколько-либо изобретательного дурака не защитит, зато читаемость кода сильно ухудшает.
51-й — ну очень спорный. Где ставить * в объявлении переменной-указателя — повод для большого холивара.
При чтении кода, взгляду проще «распарсить»
type *p_var, var;
чем
type* p_var, var;
По-православному вообще надо объявлять каждую переменную в новой строке, но не весь код «православный», да и в локальных мелких функциях/методах не всегда принято страдать «многострочием».
Кстати, рекомендации «одно объявление переменной — одна строка» я не увидел.
Я бы еще добавил правило использования в циклах преинкремента и предекремента вместо постинкремента и постдекремента (++i и --i вместо i++ и i--).
Prefer preincrement to postincrement whenever possible. Preincrement is potentially faster than postincrement. Just think about the obvious implementations of pre/post-increment. This rule applies to decrement too:

Источник: QtCreator Coding Rules
А можете пояснить, какой в этом смысл? ++i выглядит явно уродливее, чем i++ (например, если писать в столбик, то строчка выпадает из общего вертикального столбца), а по скорости работы это то же самое в любом компиляторе, выпущенному менее 25 лет назад.
Для тривиальных типов (например int) смысла в этом нет (например gcc генерирует одинаковый код ассемблера), но если используется итератор, то реализуя оператор ++ будет необходимо написать примерно следующее:
  T old( *this ); // запомнить начальное значение
  ++*this;         //инкрементировать        
  return old;     //вернуть старое значение

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

P.S. 25 лет назад кстати GCC только и появился: в 1989 году.
Понятно. Да, если нетривиальные самописные итераторы, написанные неэффективно, то конечно. Интересно, хотя бы один итератор в STL генерирует разный ассемблерный код для ++i и i++ (при условии, что возвращаемое значение не используется)?
Для такого кода
#include <vector>
#include <string>
using namespace std;
int main()
{
	vector<string> vec (1000);
	typedef vector<string>::const_iterator vi;
	for ( vi i = vec.begin() ; i != vec.end(); i++) //соответственно во втором файле меняем на преинкремент
	{
		const string& str = *i;
		string tmp = str;
	}
	return 0;
}

у меня ассемблерный код получается разный:

При этом размер файла с ассемблерным кодом составляет:
для преинкремента 37 449 байт и 1622 строк
для постинкремента 38 380 байт и 1655 строк

Если закомментировать код внутри цикла, то файлы также отличаются (при этом у постинкремента опять же больше инструкций ASM):


К сожалению я не могу найти конкретные строки в ASM так как не силен в нем.
В тему: Стандарт кодирования Google также говорит, что для итераторов следует использовать преинкремент.
Use prefix form (++i) of the increment and decrement operators with iterators and other template objects.
Небольшое дополнение ))
с ключом оптимизации -O4 ASM файлы в обоих случаях получаются идентичные.
Из этого же ряда вопрос конкатенации строк, которую можно выполнить двумя способами:
string s1 = 'a';
s1 = s1 + 'b'; //способ 1
s1 += 'b';   //способ 2

Б. Страуструп в книге «Язык программирования С++» пишет:
Оба способа добавления к концу строки семантически эквивалентны, но я предпочитаю второй, потому что он более точно отражает смысл операции и, скорее всего, реализован более эффективно.

Sign up to leave a comment.

Articles