Обсуждалось наверное миллионы раз, но в очередной раз имел спор с коллегой на эту тему, посему решил сформулировать свой подход.
Проблема: использовать ли не константные ссылки в аргументах функций?
Мой подход: нет.
Почему? Причина тут только одна: использование неконстантной ссылки для аргумента функции скрывает в вызывающем коде факт возможной модификации объекта, передаваемого в качестве параметра.
Лично нарвался недавно на собственную мину (конечно, упрощенный вариант):
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(...) ... }
Ну и чтобы два раза не вставать, пара личных маленьких радостей: