Вывести параметры и пройти по ссылке

Я присоединился к новой группе, у которой есть правила кодирования, которые (мне) кажутся датированными.

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

Руководством, которое подходит для аргумента, является:

Совет. Используйте указатели вместо ссылок для возвращаемых аргументов.

void Func1( CFoo &Return ); // bad void Func2( CFoo *pReturn ); // good 

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

Когда вы используете ссылку, она выглядит так же, как значение.

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

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

Если вы удивлены тем, что происходит, когда вы вызываете функцию, функция плохо документирована.

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

Вызывающий может невинно изменять значение без значения, чтобы повлиять на значение вызывающего абонента.

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

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

Используя указатель, как вызывающему, так и вызываемому понятно, что значение может быть изменено.

Это не совсем правильно. Функция может принимать указатель на объект const, и в этом случае объект не может быть изменен.

Использование ссылок может быть особенно ошибочным в обзорах кода.

Только если люди, проводящие обзоры кода, не знают, что они делают.


Все это хорошо и хорошо, но зачем использовать pass-by-reference вместо pass-by-pointer? Наиболее очевидная причина заключается в том, что ссылка не может быть нулевой .

В функции, которая принимает указатель, вы должны проверить, что указатель не имеет значения null до его использования, по крайней мере, с утверждением отладки. Во время правильного просмотра кода вы должны проанализировать больше кода, чтобы быть уверенным, что вы случайно не передаете нулевой указатель на функцию, которая этого не ожидает. Я обнаружил, что для просмотра функций, которые принимают аргументы указателя именно по этой причине, требуется гораздо больше времени; намного проще ошибиться при использовании указателей.

Мне кажется, что правильное использование const (в основном) устраняет необходимость в этом наконечнике. Часть, которая все еще кажется полезной, – это чтение кода вызывающего абонента, видя:

 Func1(x); 

не совсем ясно, что делается с x (особенно с Func1 именем, например Func1 ). Вместо этого:

 Func2(&x); 

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

Хотя я бы сам не использовал совет tip, обоснование действительно, поэтому на языках, подобных C #, были введены ключевые слова out и ref для использования на сайте вызова.

Самый лучший аргумент, который я могу предложить для него, заключается в следующем: вместо того, чтобы требовать от людей использования указателей, вам следует вместо этого требовать, чтобы люди записывали имена функций, которые отражают то, что делает функция . Когда я вызываю std::swap , я знаю, что это изменит значение аргументов, потому что это подразумевает это имя. С другой стороны, если бы мне пришлось вызвать функцию getSize , я бы не ожидал, что она getSize любые аргументы.

Если вы еще этого не сделали, купите экземпляр «Стандартов кодирования C ++»: «Правила, рекомендации и лучшие практики» Херба Саттера и Андрея Александреску. Прочтите. Рекомендовать его своим коллегам. Это хорошая база для локального стиля кодирования.

В правиле 25 авторы рекомендуют:

«Предпочитайте передачу по ссылке, если требуется аргумент, и функция не будет хранить указатель на нее или иным образом повлиять на ее право собственности. Это означает, что аргумент необходим и заставляет вызывающего абонента отвечать за предоставление действительного объекта».

«Аргумент необходим» означает, что NULL не является допустимым значением.

Одной из наиболее частых причин дефектов является случайная деадаптация нулевых указателей. Использование ссылок вместо указателей в этих случаях может устранить их во время компиляции.

Таким образом, у вас есть компромисс – устранение частых источников ошибок или обеспечение понятности вызова кода другими средствами, кроме имени функции. Я лично склоняюсь к устранению риска.

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

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

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

Один из возможных способов выражения параметров явно:

 template struct Out { explicit Out(T& obj) : base(obj) {} T& operator*() { return base; } T* operator->() { return &base; } private: T& base; }; template Out out(T& obj) { return Out(obj); } void f(Out n) { ++*n; } int main() { int n = 3; f(out(n)); cout << n << '\n'; } 

И как временная мера до тех пор, пока они не изменят старый код на это, вы можете сделать обратный конверт Out указателем и / или ссылкой:

 // in class definition operator T*() { return &base; } operator T&() { return base; } // elsewhere void old(int *p); void g() { int n; old(out(n)); } 

Я пошел вперед и написал различные classы, необходимые для этого, и для параметров вывода, чтобы они ухудшались. Я сомневаюсь, что я буду использовать это соглашение в ближайшее время (на C ++, по крайней мере), но он будет работать для всех, кто хочет сделать сайты вызовов явными.

Я обнаружил, что есть две школы, хотя об этом:

  • (a) использование указателя для отображения параметра может быть изменено
  • (b) использовать указатель тогда и только тогда, когда параметр может быть нулевым.

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

Я определенно вижу проблему здесь, если вы смешиваете параметры и параметры:

 bool GetNext(int index, Type & result); 

Призыв к этому значению будет выглядеть так:

 int index = 3; Type t; if (!GetNext(index, t)) throw "Damn!"; 

В этом примере сам вызов довольно очевиден, чтобы потенциально изменить t . Но как насчет index ? Может быть, GetNext увеличивает индекс, поэтому вы всегда получаете следующий элемент, без вызывающего абонента, который должен поддерживать состояние звонящего?

Обычно это вызывает ответ. Тогда метод должен быть GetNextAndIncrementIndex , или вы должны использовать iterator в любом случае . Я уверен, что этим людям никогда не приходилось отлаживать код, написанный инженерами-электриками, которые все еще думают, что Numericical Recipes – это Святой Грааль программирования.

Как бы я ни старался (б): просто потому, что проблему можно избежать при написании нового кода и «может быть нулевым или нет», как правило, более распространенной проблемой.

Обоснование логически верно.
Это может удивить кодировщиков, что значение изменилось (поскольку они считали, что значение передается по значению).

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

рекомендую:

  • передавать по ссылке (не передавать указателем)
  • перейдите по ссылке const, где это возможно (при условии, что вы правильно использовали const в своей кодовой базе)
  • помещать аргументы / параметры, которые мутируют в начале списка
  • правильно обозначить функцию
  • соответствующим образом назовите аргумент (и создайте методы / функции с подробными и описательными именами и несколькими аргументами)
  • документировать результат
  • если несколько аргументов / параметров мутируют, рассмотрим создание простого classа, который содержит эти аргументы (даже если сами ссылки)
  • если они все еще не могут функционировать (sic) без визуальных и документированных сигналов, создайте легкий контейнерный объект шаблона для параметра, который мутирует, который затем передается методу или функции

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

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

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