При таких тенденциях к обобщению возникает естественная мысль — последовать положительному примеру. Желание похвальное. Несомненно, им стоит руководствоваться при написании собственных контейнеров, итераторов и алгоритмов, но многие программисты пытаются добиться этой цели несколько иным способом. Вместо того чтобы ориентироваться на конкретный тип контейнера, они пытаются обобщить синтаксис так, чтобы в программе, например, использовался vector, но позднее его можно было бы заменить на dequeили listбез изменения кода, в котором этот контейнер используется. Иначе говоря, они пытаются писать контейнерно-независимый код. Подобные обобщения, какими бы благими намерениями они не были вызваны, почти всегда нежелательны.
Даже самый убежденный сторонник контейнерно-независимого кода вскоре осознает, что универсальный код, работающий как с последовательными, так и с ассоциативными контейнерами, особого смысла не имеет. Многие функции существуют только в контейнерах определенной категории; например, функции push_frontи push_backподдерживаются только последовательными контейнерами; функции countи lower_bound— только ассоциативными контейнерами и т. д. Даже сигнатуры таких базовых операций, как insertи erase, зависят от категории. Например, в последовательном контейнере вставленный объект остается в исходной позиции, тогда как в ассоциативном контейнере он перемещается в позицию, соответствующую порядку сортировки данного контейнера. Или другой пример: форма erase, которой при вызове передается итератор, для последовательного контейнера возвращает новый итератор, но для ассоциативного контейнера не возвращается ничего (в совете 9 показано, как это обстоятельство влияет на программный код).
Допустим, вас посетила творческая мысль — написать код, который работал бы со всеми распространенными последовательными контейнерами: vector, dequeи list. Разумеется, вам придется программировать в контексте общих возможностей этих контейнеров, а значит, функции reserveи capacity(совет 14) использовать нельзя, поскольку они не поддерживаются контейнерами dequeи list. Присутствие listтакже означает, что вам придется отказаться от оператора []и ограничиться двусторонними итераторами, что исключает алгоритмы, работающие с итераторами произвольного доступа — sort, stable_sort, partial_sortи nth_element(совет 31).
С другой стороны, исходное намерение поддерживать vectorисключает функции push_frontи pop_front; vectorи dequeисключают применение spliceи реализацию sortвнутри контейнера. Учитывая те ограничения, о которых говорилось выше, последний запрет означает, что для вашего «обобщенного последовательного контейнера» не удастся вызвать никакую форму sort.
Пока речь идет о вещах простых и очевидных. При нарушении любого из этих ограничений ваша программа не будет компилироваться по крайней мере для одного из контейнеров, которые вы намеревались поддерживать. Гораздо больше проблем возникнет с программами, которые будут компилироваться.
В разных последовательных контейнерах действуют разные правила недействительности итераторов, указателей и ссылок. Чтобы ваш код правильно работал с vecto r , dequeи list, необходимо предположить, что любая операция, приводящая к появлению недействительных итераторов, указателей и ссылок в любом из этих контейнеров, приведет к тем же последствиям и в используемом контейнере. Отсюда следует, что после каждого вызова insertнедействительным становится абсолютно все, поскольку deque::insertделает недействительными все итераторы, а из-за невозможности использования capacityприходится предполагать, что после операции vector::insertстановятся недействительными все указатели и ссылки (как упоминается в совете 1, контейнер dequeобладает уникальным свойством — в некоторых случаях его итераторы могут становиться недействительными с сохранением действительных указателей и ссылок). Аналогичные рассуждения приводят к выводу, что после каждого вызова eraseвсе итераторы, указатели и ссылки также должны считаться недействительными.
Недостаточно? Данные контейнера не передаются через интерфейс C, поскольку данная возможность поддерживается только для vector(совет 16). Вы не сможете создать экземпляр контейнера с типом bool— как будет показано в совете 18, vectorне всегда ведет себя как vectorи никогда не хранит настоящие логические величины. Вы даже не можете рассчитывать на постоянное время вставки-удаления, характерное для list, поскольку в vectorи dequeэти операции выполняются с линейной сложностью.
Читать дальше