Подтвердить что ты не робот

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

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


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

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

Ответ 2

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

Func1(x);

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

Func2(&x);

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

Ответ 3

В то время как я не использовал сам совет, оправдание действительно, поэтому языки, такие как С#, вводили ключевые слова out и ref для использования на сайте вызова.

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

Ответ 4

Если вы еще этого не сделали, купите экземпляр Herb Sutter и Andrei Alexandrescu "Стандарты кодирования С++: 101 Правила, рекомендации и лучшие практики". Прочтите. Рекомендовать его своим коллегам. Это хорошая база для локального стиля кодирования.

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

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

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

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

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

Ответ 5

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

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

template<class T>
struct Out {
  explicit Out(T& obj) : base(obj) {}
  T& operator*() { return base; }
  T* operator->() { return &base; }
private:
  T& base;
};
template<class T>
Out<T> out(T& obj) {
  return Out<T>(obj);
}

void f(Out<int> 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));
}

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

Ответ 6

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

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

Ответ 7

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

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

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

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

bool GetNext(int index, Type & result);

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

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

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

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

Howver. Я все еще склонен к (б): просто потому, что проблему можно избежать, если писать новый код и "может быть нулевым или нет", как правило, является более распространенной проблемой.

Ответ 8

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

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

Ответ 9

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

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

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

Ответ 10

Я рекомендую:

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