В продолжение недавно начатой темы. Есть очень удобная идиома copy-then-swap, которая позволяет легко и просто написать для своего типа оператор копирования, обеспечивающий строгую гарантию безопасности исключений.
Для примера рассмотрим некий вымышленный тип, который содержит внутри пару векторов:
|
class special_container { struct description { ... }; struct payload { ... }; std::vector<description> m_descriptions; std::vector<payload> m_payloads; ... }; |
И мы хотим, чтобы у special_container был оператор копирования со строгой гарантией безопасности исключений. Для этого нам потребуются:
- обычный конструктор копирования;
- не бросающий исключений swap.
что достигается весьма просто:
|
class special_container { ... public: // Swap сделаем через свободную функцию. friend void swap(special_container & a, special_container & b) noexcept { using std::swap; swap(a.m_descriptions, b.m_descriptions); swap(a.m_payloads, b.m_payloads); } // Конструктор копирования. special_container(const special_container & other) : m_descriptions{ other.m_descriptions } , m_payloads{ other.m_payloads } {} ... }; |
Имея в своем распоряжении эти базовые инструменты можно сделать и оператор копирования:
|
special_container & special_container::operator=(const special_container & other) { special_container tmp{ other }; swap(*this, tmp); return *this; } |
Фокус здесь в том, что возможные исключения вылетят при формировании объекта tmp. Но при этом ничего не меняется в this. А если при конструировании tmp исключений не случилось, то мы заменяем содержимое this содержимым tmp.
Еще один приятный фокус в том, что такая примитивная реализация прекрасно защищает и от присваивания самому себе. Впрочем, если экземпляры special_container "тяжелые", а вероятности самоприсваивания не нулевая, то можно и по старинке:
|
special_container & special_container::operator=(const special_container & other) { if(this != std::addressof(other)) { special_container tmp{ other }; swap(*this, tmp); } return *this; } |
Пока что все идет замечательно.
Но давайте представим себе, что нам потребовалось научить special_container работать с разными аллокаторами. Т.е. тип special_container превращается во что-то вроде:
|
template<typename Alloc> class special_container { struct description {}; struct payload {}; using alloc_traits = std::allocator_traits<Alloc>; using description_allocator = alloc_traits::template rebind_alloc<description>; using payload_allocator = alloc_traits::template rebind_alloc<payload>; std::vector<description, description_allocator> m_descriptions; std::vector<payload, payload_allocator> m_payloads; ... }; |
Сможем ли мы и дальше пользоваться идиомой copy-then-swap?
И вот тут у меня есть сомнения. А в попытках разобраться как раз и получился этот пост.
У аллокатора может быть такое свойство как propagate_on_container_swap. Если это свойство выставлено в std::true_type, то при выполнении swap мы можем обменять аллокаторы для контейнеров.
Грубо говоря, допустим, что у нас есть собственный тип аллокатора: