*** ВНИМАНИЕ: Блог переехал на другой адрес - demin.ws ***

среда, 17 ноября 2010 г.

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

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

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

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

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

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

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. Наши ребята сделали отличный сайт для контестов и регулярно их там проводят. В отличие от ТопКодера русский язык там в почете, задания предоставляются на обоих языках, и выбор язык программирования гораздо шире. Также можно там спросить совета по задачам и получить квалифицированный ответ от бойцов.

4 комментария:

  1. Александр, а почему не участвуете в марафонах? Там очень интересные задачи. И они больше, на мой взгляд, приближены к реалиям, чем в algorithm contest.

    ОтветитьУдалить
  2. @Evgeny Baskakov: пока просто не пробовал.

    ОтветитьУдалить
  3. А я предпочитаю не заниматься работой компилятора:
    const T a = 10;
    ...
    f(a); // Ошибка компиляции

    ОтветитьУдалить
  4. Я стараюсь использовать только константные ссылки. Подсознательно для себя уже завел правило, схожее с гугловыми гайдами по разработке на С++ - если ф-ция не изменяет входящую переменную - передаем как константную ссылку, если меняет - как указатель. К тому же надо помнить про правило, что жизнь временного объекта может продлиться на время жизни константной ссылки на него, а бывает очень удобно при вызове ф-ции написать что-то вроде f( T(10) );

    ОтветитьУдалить