Я привел эту задачу по двум причинам. Во-первых, она напоминает вам о существовании очень удобной функции assign
, о которой многие программисты попросту забывают. Функция assign
поддерживается всеми стандартными последовательными контейнерами ( vector, string, deque
и list
). Каждый раз, когда вам требуется полностью заменить содержимое контейнера, подумайте, нельзя ли добиться желаемой цели присваиванием. Если вы просто копируете один контейнер в другой контейнер того же типа, задача решается функцией operator=
. Но, как показывает приведенный пример, существует также функция assign
, которая позволяет заполнить контейнер новыми данными в тех случаях, когда operator=
не подходит.
Во-вторых, эта задача показывает, почему интервальные функции лучше своих одноэлементных аналогов. Интервальной называется функция контейнера, которая, подобно алгоритмам STL, определяет интервал элементов для выполняемой операции при помощи двух параметров-итераторов. Без интервальной функции нам пришлось бы создавать специальный цикл:
vector v1,v2; // Предполагается, что v1 и v2 -
// векторы объектов Widget
…
v1.clear():
for (vector::const_iterator ci=v2.begin()+v2.size()/2; ci != v2.end(); ++ci)
v1.push_back(*ci);
В совете 43 подробно объясняется, почему использовать явные циклы не рекомендуется, но и без этого ясно, что написание этого фрагмента потребует больше усилий, чем простой вызов assign
. Цикл также отрицательно влияет на быстродействие, но к этой теме мы вернемся позже.
Одно из возможных решений заключается в том, чтобы последовать совету 43 и воспользоваться алгоритмом:
v1.clear();
copy(v2.begin()+v2.size()/2, v2.end(), back_inserter(v1));
Но и этот вариант требует больших усилий, чем простой вызов assign
. Более того, хотя цикл не встречается в программе, он наверняка присутствует внутри вызова copy
(см. совет 43). В результате потенциальное снижение быстродействия не исчезает (вскоре мы поговорим об этом). А сейчас я хочу ненадолго отвлечься от темы и заметить, что практически все случаи использования copy
, когда приемный интервал задается итератором вставки ( inserter, back_inserter
или front_inserter
), могут — и должны — заменяться вызовами интервальных функций. Например, вызов copy
заменяется интервальной версией insert
:
v1.insert(v1.end(), v2.begin()+v2.size()/2. v2.end());
Команда получается ненамного короче, но она к тому же ясно указывает на суть происходящего: данные вставляются в v1
. Вызов copy
означает примерно то же, но не столь очевидно. В данном случае важно не то, что элементы копируются, а то, что в v1
добавляются новые данные. Функция insert
прямо говорит об этом, а copy
лишь сбивает с толку. Нет ничего особенно интересного в том факте, что данные где-то копируются, — собственно, вся библиотека STL построена на принципе копирования. Копирование играет настолько важную роль в STL, что ему посвящен совет 3.
Многие программисты STL злоупотребляют функцией copy
, поэтому только что данный совет стоит повторить: вызовы copy, в которых результирующий интервал задается итератором вставки, практически всегда следует заменять вызовами интервальных функций.
Вернемся к примеру с assign
. Мы уже выяснили две причины, по которым интервальным функциям отдается предпочтение перед их одноэлементными аналогами.
• Написание кода с интервальными функциями обычно требует меньших усилий.
• Решения с интервальными функциями обычно выглядят более наглядно и логично.
Короче говоря, программы с интервальными функциями удобнее как писать, так и читать. О чем тут еще говорить?
Впрочем, некоторые склонны относить эти аргументы к стилю программирования, а вопросы стиля вызывают у программистов такую же жаркую полемику, как и тема выбора Лучшего В Мире Редактора (хотя о чем тут спорить? Всем известно, что это Emacs
). Было бы неплохо иметь более универсальный критерий для сравнения интервальных функций с одноэлементными. Для стандартных последовательных контейнеров такой критерий существует: эффективность. При работе со стандартными последовательными контейнерами применение одноэлементных функций приводит к более частому выделению памяти, более частому копированию объектов и/или выполнению лишних операций по сравнению с реализацией, основанной на интервальных функциях.
Предположим, вы хотите скопировать массив int
в начало vector
(исходное размещение данных в массиве может объясняться тем, что данные были получены через унаследованный интерфейс с языком С. Проблемы, возникающие при объединении контейнеров STL с интерфейсом C, описаны в совете 16). Решение с интервальной функцией insert
контейнера vector
выглядит просто и бесхитростно:
Читать дальше