Например, вместо:
void f(T& t) {
// change ‘t’
}
...
T a;
f(a);
я предпочту передачу по указателю:
void f(T* t) {
// change ‘*t’
}
...
T a;
f(&a);
Мой основной мотив – наглядность в вызывающем коде. Когда я вижу «&» перед аргументом, я точно знаю, что это возвращаемый параметр, и он может измениться после вызова. И мне не надо постоянно помнить, какие именно агрументы у этой функции ссылки, а какие нет.
Конечно тут есть и минусы: корявость текста в вызываемом коде, так как надо таскать за собой «*» или «->». Также неплохо бы проверять указатель на ноль.
А у вас есть предпочтения в этом вопросе?
Из-за необходимости добавлять assert для указателей в каждую функцию предпочитаю ссылки.
ОтветитьУдалить// Пример:
void f(T const & input, T & output);
Если аргумент необязательный - тогда через указатель.
P.S. Отличный блог, с интересом читаю )
Как и someone.
ОтветитьУдалитьКстати, вакансии блумберга еще актуальны?
Верно подмечено... Я тоже стараюсь придерживаться этого правила, а еще лучше не использовать изменяемые параметры. :) Но никогда не задумывался о том что вся соль в том, что параметр-указатель заставляет программиста сознательно указывать это при вызове!
ОтветитьУдалитьВ Google C++ Style Guide написано то же самое. Кроме морали. :)
И конечно ссылки всегда константные!
когда имя функции становится более осознанным, становится понятно что она делает и что возвращает...
ОтветитьУдалитьдопустим Read(stream, a) Write( stream, a )...
2Pushkoff: И какой же из этих параметров (stream, a) в каком случае модифицируется?
ОтветитьУдалитьПо моему они одинаковые. :)
@Nick: Да, но все меняется каждый день, так что тянуть не стоит. По тем ссылкам всегда актуальная информация.
ОтветитьУдалить@Pushkoff: Да, потоки - это пожалуй единственное исключение из правил для меня.
ОтветитьУдалитьС моей тоски зрения, использование ссылки для предечи агрумента имеет один плюс - изменяемый параметр будет обязательным т.е. у пользователя ф-ции не будет возможности передать несуществующий объект(не использую хаков). Это особенно важно когда ф-ция не имеет смысла без аргументов.
ОтветитьУдалить2Kirill: Константный! :)
ОтветитьУдалитьТут речь идет о вводе параметров в функцию. Константные ссылки рулят почти всегда. (Параметрами по умолчанию ИМХО тоже не стоит злоупотреблять)
А вывод лучше стараться все же делать через возвращаемое значение - это вполне однозначно.
Если возникает необходимость возвращать из функции несколько значений, может быть функция пытается делать слишком многое?
И конечно всегда бывают исключения. В исключительных ситуациях можно и ассерт поставить. :)
Этот комментарий был удален автором.
ОтветитьУдалить>Google C++ Style Guide
ОтветитьУдалитьбыл бы нормальным документом, если не бы не совершенно идиотский запрет на исключения. Вот как, скажите на милость, юзать RAII и сообщать об ошибках из конструкторов?
@Tier: Как я понимаю, есть определенный подход, когда конструктор делает только примитивные инициализации. Сложные логические инициализации делаются явным вызовом функций типа init, геттеров и сеттеров. Это позволяет при unit тестировании более удобно работать с классом. А если в конструкторе таки возникают исключения, то он их сам ловит и устанавливает флаг типа bad, который можно проверить извне. Противоречивая практика, но вполне жизненная. Весь Хром так написан. Кстати, его исходники - это кладезь всяких штучек и приемов.
ОтветитьУдалитьКстати, юзать RAII можно и не имея исключений.
ОтветитьУдалить>Как я понимаю, есть определенный подход, когда конструктор делает только примитивные инициализации. Сложные логические инициализации делаются явным вызовом функций типа init, геттеров и сеттеров.
ОтветитьУдалитьОчень мило... Т.е. в каждом методе я ещё и проверять должен, удосужился ли пользователь моего класса вызвать init? Правильный подход - объект либо создан, либо нет. Всё. Нельзя "наполовину родиться".
>Это позволяет при unit тестировании более удобно работать с классом.
Уууууу... Обещали облегчение жизни при юнит тестировании, а тут ещё и менять стиль написания программ надо? Либо юнит тестирование подстраивается под RAII, либо идёт лесом, полем...
>А если в конструкторе таки возникают исключения, то он их сам ловит и устанавливает флаг типа bad, который можно проверить извне.
Ещё милее... Т.е. это пользователь будет проверять объект на валидность? А если забыл, и объект наполовину создан, то можем получать странное поведение, в зависимости от того, как проинициализировались мемберы. Веселуха :D Особенно это будет заметно при подобной забывчивости в юнит тесте - один раз тест прошёл, а второй раз - нет.
>Весь Хром так написан.
Ну, могу выразить свои соболезнования писавшим Хром :)
>Кстати, юзать RAII можно и не имея исключений.
Можно код?
Кстати, предложенное вами ничем не отличается от такого набора
ОтветитьУдалитьf(MyMegaStruct*, etc...)
g(MyMegaStruct*, etc...)
h(MyMegaStruct*, etc...)
На кой вам сдали классы с init'ами - я не понимаю...
@Tier
ОтветитьУдалитьА по-моему наличие исключений - идиотская фича C++.
Сколько людей - столько мнений.
В минусы исключений тоже много чего можно написать - например, наличие еще одной НЕЯВНОЙ точки выхода из функции. Поведение кода становится попросту непредсказуемым, уже этого достаточно, чтобы отказаться их использовать. Да в Google Style очень хорошо написано, почему они не рекомендуют ими пользоваться.
>А по-моему наличие исключений - идиотская фича C++.
ОтветитьУдалитьИ вам я тоже соболезную :)
>Да в Google Style очень хорошо написано, почему они не рекомендуют ими пользоваться.
Как я понял, основные грабли, которые им не нравятся, отнюдь не читаемость, а необходимость работы с либами, которые эти исключения не используют.
@Tier
ОтветитьУдалить> И вам я тоже соболезную :)
А я вам :-)
Очень сложно поддерживать код с исключениями в рабочем состоянии и передавать его другому разработчику для поддержки, просто потому что - действие в одном месте, а обработка возникшей ошибки может лежать где угодно. И фактически нет способов найти место обработки, кроме как его сесть и трассировать по шагам.
2MaMoHT:
ОтветитьУдалить>просто потому что - действие в одном месте, а обработка возникшей ошибки может лежать где угодно
Далеко не всегда при возникновении некоторой ситуации можно заранее решить что с ней делать. Обычно это определяется логикой обработки ошибок приложения.
Исключения очень удобны хотя бы тем, что не требуют анализировать ошибки там, где всеравно ничего с ними поделать нельзя.
Функция printf возвращает код ошибки... кто нибудь его анализирует? А если бы она бросала бы исключения (предположим что она плюсовая) - проигнорировать это было бы невозможно.
Исключения хороши тем, что их нельзя просто проигнорировать.
Кроме того, вместо кодов ошибок из функций можно возвращать естественным путем более полезную информацию, не заморачиваясь с сабжем. :)
@Tier: По поводу RAII, например, это. Это тривиальный wrapper для мютексов. Там нет никаких исключений, а RAII используется.
ОтветитьУдалитьКак-то сложно обвинять в людей, написавших за столь короткое время лучший в мире опенсорсный браузер, что они как-то недопонимают, как писать большие и сложные проекты на С++. У меня нет причин делать это. Я весьма внимательно изучал код Хрома и мне совершенно не на что пожаловаться. Все б так писали.
А по поводу изменения стиля для использования тестирования -- ответ "да". Ни одна утилита не протестирует код, если программист написал его абсолютно нетестируемым. А внесение в конструкторы сложной логики, особенно привязка к внешним объектам, делает такой класс не тестируемым. Если получается, что чтобы код стал тестируемым надо менять стиль -- может стиль был неверный? и его стоит поменять.
>действие в одном месте, а обработка возникшей ошибки может лежать где угодно
ОтветитьУдалитьИ? А при кодах ошибок не так, что ли? Вот есть у вас либа, которой на вход даётся путь до файла. И вот не получилась у вас её открыть. И что? Вы будете обрабатывать ошибку прямо здесь? А как вы сообщите об этом пользователю? На консоль? Или на GUI? А как вы у него попросите правильный путь?
Нет ведь! Вы передадите код ошибки выше. И "там" уже решат как жить дальше. Это, вообще-то, правильная стратегия.
>По поводу RAII, например, это.
У вас там
AutoLock(Mutex& lock) : __lock(lock) {
__lock.Lock();
}
А в Mutex'е
void Mutex::Lock() { pthread_mutex_lock(&__mutex); }
Т.е. никакого кода ошибки не возвращается. Простите, а он всегда в посиксе лочится? В смысле, ЕМНИП, может быть и ошибка. А меж тем ни Mutex::Lock(), ни конструктор AutoLock об этом никак не сообщают.
>Как-то сложно обвинять в людей, написавших за столь короткое время лучший в мире опенсорсный браузер, что они как-то недопонимают, как писать большие и сложные проекты на С++.
Ну, во-первых, давайте ещё холивар про "лучший браузер" здесь разводить не будем ;)
Во-вторых, не надо давить авторитетом. Типа "они крутые пацаны, я буду делать как они". Какой-никакой, а я инженер. И меня интересуют доводы, а не авторитеты. Пока что мне так и не объяснили, чем набор функций по работе со структурой хуже класса, где инициализация вызывается отдельно от конструктора.
В-третьих, я не говорил, что программисты гугла не умеют делать большие проекты. Их аргументацию про либы без исключений я понял. А вот 1) как использовать RAII без исключений и 2) почему _все_ проекты на плюсах не должны их юзать - нет.
По поводу unit тестов - я не против, что интерфейс класса должен быть таким, чтобы его можно было разумным образом протестировать. Но, простите, отказываться от инициализации в конструкторе и исключений - это перебор.
ОтветитьУдалить2Tier:
ОтветитьУдалить>>действие в одном месте, а обработка возникшей ошибки может лежать где угодно
>И? А при кодах ошибок не так, что ли?
Вот и я про то же... ошибка обрабатывается совершенно не там где возникла, а там где можно с этим что-то сделать.
Пользуясь кодами ошибок - перед нами стоит задача - бережно сохранить и донести смысл ошибки до того места, где можно на нее среагировать. То есть ее надо передавать из функции в функцию, из библиотеки в программу... все равно этим полноценно никто не занимается.
В то время как исключения совершенно снимают с нас эту головную боль, кроме того дают нам возможность максимально обогатить информацию об ошибке.
Исключения однозначно рулят. Но конечно, если код is not exception-tolerant, как говорят в Google, то использование исключений может вызвать различные проблемы. Но разве это проблема исключений?
+1 предыдущему оратору :)
ОтветитьУдалитьПо хорошему, вызов pthread_mutex_lock надо обернуть в assert, так как если не работает pthread_mutex_lock, то программе нечего дальше делать вообще.
ОтветитьУдалитьКстати, я не отказывался от исключений как таковых -- мой основной посыл - конструктор должен позволять себе тестировать. Если в нем несколько сотен строк, с исключениями (особенно не критическими, а несущими типа "полезную нагрузку" в виде кодов ошибок) и вызовами других функций - это не очень хорошо.
Поэтому лично я стараюсь делать так, что конструктор может "упасть" или даже бросить исключение только из-за ошибок распределения памяти. Как раз для того, чтобы не быть рожденым наполовину. Все касаемо бизнес-логики - это уже не ответственность конструктора. Физически создав объект, он свою часть работы выполнил. Далее уже сеттеры и геттеры или функции логической инициализации.
>По хорошему, вызов pthread_mutex_lock надо обернуть в assert, так как если не работает pthread_mutex_lock, то программе нечего дальше делать вообще.
ОтветитьУдалитьНеужели? Даже если это в плагине? Я должен всё положить ради него?
И вообще, assert, невозбранно роняющий систему - это то ещё зло, которое даже ошибку в лог положить не позволяет. Или логи у Ъ парней из гугла тоже не в ходу? :)
>Все касаемо бизнес-логики - это уже не ответственность конструктора. Физически создав объект, он свою часть работы выполнил.
Я всё же считаю, что _физически_ объект создаёт аллокатор. Ибо _физически_ он не более, чем кусок памяти. А вот всё остальное - уже логика. И именно поэтому "получение ресурса есть инициализация".
Остальной текст на мой взгляд является здравой логикой. же говорю о том, что гугловский гайд как лекарство и панацея, мягко говоря, не выдерживает критики. Не годен он для всех проектов на плюсах. Из-за тех же исключений. Из-за неиспользования RTTI. Вот мне, например, надо было сделать множественную диспетчеризацию. Как её сделать без RTTI? Паттерн Visitor не предлагать, ибо требуется пересборка всего при добавлении нового класса, а меня это не устраивает...
Не ожидал, что такой безобидный пост вызовет такую горячую дискусию.
ОтветитьУдалитьСобственно 5 копеек от меня: в моей практике исключения еще не приносили вреда, а вот из-за использования кодов ошибки был случай когда объект был не инициализирован после конструирования (тоже что-то мютексами было).
Я избегаю исключений. Error codes + init() FTW.
ОтветитьУдалитьО недостатках исключений здесь: http://yosefk.com/c++fqa/exceptions.html
Вообще весь FQA - полезное чтиво.
Прислали в комментариях интересную ссылку (непонятно, почему не отображается сам комментарий): http://yosefk.com/c++fqa/exceptions.html. Например, в п.17.2 есть неплохие аргументы против бросания сложных исключений в конструкторах.
ОтветитьУдалить