Неконстантные ссылки

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

Проблема: использовать ли не константные ссылки в аргументах функций?

Мой подход: нет.

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

Лично нарвался недавно на собственную мину (конечно, упрощенный вариант):

T a = 10;
...
f(a);
...
assert(a == 10); // BOOM!!! WTF?! Who changed this?

А причина в void f(T& a);, а должно быть void f(const T& a); или void f(T* a);. Функция f() почему-то изменила значение «а», а писал я ее давно и успел забыть такую ее «особенность». Но из кода f(a) сходу не видно – может эта функция изменить а или нет.

А как могло произойти, что мне вообще пришло в голову сделать параметр a неконстантной ссылкой? Лично у меня это случается, когда переменная изначально была внутри функции, и в какой-то момент я решил сделать ее параметром, а менять в коде везде a. на a-> было просто лень, вот и сделал ссылку, вместо указателя. За что и поплатился, позже.

Кстати, один из аргументов, приводимый людьми, выступающими за неконстантные ссылки –это «писать a. приятнее и понятнее, чем a-> или *a». Также ссылка более надежна с точки зрения NULL (сделать ссылку, указывающую на NULL конечно можно, но тут уже надо постараться). Тут можно выйти положения так:

void f(T* ptr_a) {
  assert(ptr_a != NULL);
  T& a = *ptr_a;
  ...
  if (a.foo()) ...
}

Небольшой лишний код, но решены обе проблемы: проверка на NULL и необходимость разыменовывать указатель каждый раз. А главное, в вызывающем коде придется писать так: f(&a), что явно укажет на факт возможной модификации аргумента внутри функции.

Например, в C# есть специальной ключевое слово ref, которое надо ставить перед аргументами в вызывающем коде, если хочется передать что-то по ссылке. По мне,это очень хорошее свойство языка.

Исключения из правила

Я пока выработал для себя одно исключение: сложные, сервисные прокси-объекты, типа потока или базы данных можно передавать по неконстантной ссылке. Но тут надо четко понимать, не вы меняете этот объект внутри функции, а он меняется сам при обращении к нему.

Например:

void print(std::ostream& os, const T& a) {
   os << a;
}

или

void save_to_db(Database& db, ...) {
   db.connect();
   db.save(...)
   ...
}

Ну и чтобы два раза не вставать, пара личных маленьких радостей:

  • Мое интервью проекту “OpenQuality”.
  • Уверенно решил две задачи в недавней SRM 487. А достижение в том, что во второй задаче даже применил DP, хоть и тривиальное. В процессе контеста был последним в комнате, так как долго возился с первой задачей, но потом почти все упали на фазе challenge’а и на тестах, и я оказался вторым в комнате. Кстати, настоятельно рекоммендую сайт CodeForces. Наши ребята сделали отличный сайт для контестов и регулярно их там проводят. В отличие от ТопКодера русский язык там в почете, задания предоставляются на обоих языках, и выбор язык программирования гораздо шире. Также можно там спросить совета по задачам и получить квалифицированный ответ от бойцов.

Оригинальный пост | Disclaimer

Комментарии