…
Часть 5. Функции
Часть 6. Специфика Google
Часть 7. Ещё возможности C++
…
Эта статья является переводом части руководства Google по стилю в C++ на русский язык.
Исходная статья (fork на github), обновляемый перевод.
Специфика Google
Есть различные трюки и средства, которые используются чтобы сделать код на C++ более надёжным. И да, они могут отличаться от того, что используют в других компаниях.
Владение и умные указатели
Предпочтительно, чтобы динамически созданный объект имел одного (выделенного) владельца. Передачу такого «владения» желательно проводить через умные указатели.
Определение
«Владение» это технология учёта, используемая для управления динамически выделенной памятью или другими ресурсами. Владелец динамической сущности (объекта) это объект или функция, которые ответственны за удаление сущности, когда она будет не нужна. Владение может быть распределённым, и в этом случае обычно последний оставшийся владелец отвечает за удаление. Даже если владение не является распределённым, этот механизм может использоваться, чтобы передать владение от одного объекта (или кода) другому.
«Умные» указатели это классы, которые функционируют как обычные указатели; например, в них перегружены операторы * и ->. Некоторые типы умных указателей можно использовать для автоматического управления «владением»: учёт владельцев, удаление объектов. std::unique_ptr это умный указатель, добавленный в C++11, который реализует эксклюзивное владение (только один владелец); объект удаляется в случае выхода из области видимости экземпляра std::unique_ptr. std::unique_ptr не может быть скопирован, однако его можно передать (move) другому std::unique_ptr, что фактически есть передача владения. std::shared_ptr это умный указатель, реализующий распределённое владение. std::shared_ptr можно копировать, при этом владение распределяется между всеми копиями. Управляемый объект удаляется, когда разрушается последняя копия std::shared_ptr.
За
- Очень тяжело управлять динамической памятью без какого-либо механизма учёта владения.
- Передача владения может быть проще и быстрее, чем копирование объекта. Также не все объекты можно скопировать.
- Передача владения может быть проще, чем копирование указателя или использование ссылок, т.к. нет необходимости согласовывать жизненный цикл объекта между различными частями кода.
- Умные указатели могут улучшить читабельность кода, сделать его логику более понятной, самодокументируемой и непротиворечивой.
- Умные указатели могут исключить ручное управление владением, упростить код и избежать большого количества ошибок.
- Для константных объектов распределённое владение может быть простой и эффективной альтернативой против полного копирования.
Против
- Владение должно представляться и передаваться через указатели (любо умные, либо обычные). Семантика указателей сложнее работы со значениями, особенно в API: помимо владения необходимо беспокоиться о правильности используемых типов (aliasing), времени жизни, изменяемости объектов и т.д.
- Затраты по производительности при копировании значений часто завышены, поэтому прирост производительности при передаче владения (против простого копирования) в ряде случаев не может оправдать ухудшение читабельности и увеличение сложности кода.
- API управления владением могут накладывать свои ограничения на модель (порядок) управления памятью.
- При использовании умных указателей нет чёткого понимания где именно (в коде) будет производится освобождение ресурсов.
- std::unique_ptr реализует передачу владения через семантику перемещения C++11, которая является новой и может затруднить понимание кода.
- Распределённое владение с одной стороны позволяет аккуратно управлять владением, с другой стороны может усложнить архитектуру системы.
- Распределённое владение требует операций учёта во время выполнения, это может отразиться на производительности.
- В ряде случаев (например, создании циклических ссылок) объекты с распределённым владением никогда не удалятся.
- Умные указатели не всегда могут заменить обычные указатели.
Вердикт
Если необходима работа с динамической памятью, то предпочтительно чтобы код, выделяющий память, ею же и владел. Если другой код хочет получить доступ к этой памяти, то можно передать копию, указатель или ссылку (и всё это без передачи владения). Предпочтительно использовать std::unique_ptr для явной передачи владения. Например:
std::unique_ptr<Foo> FooFactory();
void FooConsumer(std::unique_ptr<Foo> ptr);
Без существенной причины не проектируйте (не используйте) код с распределённым владением. Как вариант, это может быть желание избежать «тяжелой» операции копирования, однако обязательно убедитесь, что выигрыш будет существенным и разделяемый объект — неизменяемый (т.е. std::shared_ptr<const Foo>). Если же требуется именно распределённое владение, то используйте std::shared_ptr.
Никогда не используйте std::auto_ptr. В качестве замены есть std::unique_ptr.
cpplint
Для проверки кода на ошибки стиля используйте cpplint.py.
cpplint.py — утилита, которая читает файл с кодом и определяет многие ошибки в стиле. Конечно, она не идеальна, иногда выдаёт ложно-положительные и ложно-отрицательные ошибки, однако это всё равно полезная утилита. Ложно-положительные ошибки можно исключить, если вставлять // NOLINT в конце строчки кода или // NOLINTNEXTLINE на предыдущей строке.
Иногда в проекте есть инструкция, откуда брать и как пользоваться cpplint.py. Если в вашем проекте такой нет, то можно просто скачать cpplint.py.
Примечания:
Изображение взято из открытого источника.
Комментарии (8)
DistortNeo
20.10.2021 23:27+2std::unique_ptr реализует передачу владения через семантику перемещения C++11, которая является новой и может затруднить понимание кода.
А ничего, что с момента введения семантики перемещения прошло уже больше 10 лет?
tzlom
21.10.2021 09:16+1Реалии таковы что 80% разработчиков на C++ с 3 годами коммерческого опыта (не институт) не могут написать шаблон использующий SFINAE (хотя и в курсе что такое существует) и будут избегать самописных шаблонов в принципе, обычно с аргументацией о поддерживаемости другими людьми. Прошло уже больше 20 лет.
DistortNeo
21.10.2021 11:42А я попросту откажусь писать шаблон с SFINAE. Максимум согласен на
std::enable_if
, но не более того. Считаю, что это крайне неудобный и плохо читаемый костыль, для которого есть хорошие альтернативы в виде constexpr и концептов.mk2
21.10.2021 20:05Если в организации перешли на C++17, то несомненно. А если готовы на C++20, то можно и на концепты посмотреть.
oleg-m1973
Обычно вердикт, это компромисс между аргументами "за" и аргументами "против". Т.е. он должен отвечать на вопрос - почему так, а не иначе. А здесь - вердикт (худо-бедно верный) сам по себе, аргументы сами по себе.
Ещё раз скажу, что нужно учитывать, что Гугл - это гигантская корпорация, которая может позволить себе программировать в плохом стиле.
DarkEld3r
Предположим, но зачем им это делать? Разве должно быть не наоборот? В смысле, если "хороший стиль" даёт выигрыш в 1% (для примера и не важно чего именно: скорости, поддерживаемости или чего-то ещё), то на объёмах гугла разве это не будет куда более заметно, чем в мелком стартапе?
oleg-m1973
Стиль, он позволяет снизить трудозатраты на поддержку, сопровождение, доработку и т.д. По опыту - для крупных компаний проще нанять пару-тройку-десяток разработчиков, чем заморачиваться со стилем (что не так просто, как кажется).
DarkEld3r
Тут согласен, но компании вроде гугла заморачиваются написанием собственных инструментов, библиотек и даже языков. Кажется странным игнорировать стиль — уж деньги там считать должны уметь.