Move semantics в C++11 и STL-контейнеры

от автора

Эта небольшая заметка о том, как с приходом нового стандарта C++11 изменились требования стандартных контейнеров к своим элементам. В C++98 от элемента контейнера требовалось, по сути, наличие «разумных» конструктора копирования и оператора присваивания. Если, например, объект вашего класса владеет каким-либо ресурсом, копирование обычно становится невозможным (по крайней мере, без «глубокого» копирования ресурса). В качестве примера давайте рассмотрим следующий класс-обертку вокруг FILE*, написанную на C++98:

class File {     FILE* handle; public:     File(const char* filename) {         if ( !(handle = fopen(filename, "r")) )             throw std::runtime_error("blah blah blah");     }     ~File() { if (handle) fclose(handle); }     // ... private:     File(const File&); //запретить копирование     void operator=(const File&); //запретить присваивание }; 

Мы запретили копирование и присваивание объектов этого класса, поскольку копирование FILE* потребовало бы некоторых платформо-зависимых ухищрений, и вообще не имеет особого физического смысла.

Что же делать, если требуется хранить целый список объектов типа File? К сожалению, мы не можем использовать File в стандартном контейнере, то есть такой код просто не скомпилируется:

std::vector<File> files; files.push_back(File("data.txt")); 

Типичным решением такой проблемы в C++98 является использование shared_ptr:

std::vector<boost::shared_ptr<File> > files; files.push_back(boost::shared_ptr<File>(new File("data.txt")) ); 

Такое решение не особо радует глаз, особенно учитывая то, что мы используем динамическую память там, где, казалось бы, она не нужна.

Если мы разрешаем использование C++11, то картина сильно меняется. С появлением move semantics, стандартные контейнеры больше не требуют наличия обычных конструктора копирования и оператора присваивания, если только вы не собираетесь копировать контейнер целиком. Вместо них достаточно наличия семантики перемещения. Давайте посмотрим, как мы можем переписать пример с классом File на C++11:

class File {     FILE* handle; public:     File(const char* filename) {         if ( !(handle = fopen(filename, "r")) )             throw std::runtime_error("blah blah blah");     }     ~File() { if (handle) fclose(handle); }      File(File&& that) {         handle = that.handle;         that.handle = nullptr;     }      File& operator=(File&& that) {         std::swap(handle, that.handle);         return *this;     }      File(const File&) = delete; //запретить копирование     void operator=(const File&) = delete; //запретить присваивание      // ... }; 

Мы снова запрещаем обычное копирование, но разрешаем перемещение объекта. Теперь такой код работает:

std::vector<File> files; files.push_back(File("data1.txt")); files.push_back(File("data2.txt")); files.erase(files.begin()); 

Кроме того, благодаря variadic templates, в контейнерах появилась новая шаблонная функция emplace_back, которая позволяет создать объект прямо в контейнере, не копируя его:

std::vector<File> files; files.emplace_back("data1.txt"); // добавить File("data1.txt") в конец массива 

Надеюсь, что данная заметка наглядно показывает, насколько важным нововведением является семантика перемещения объектов. Всем успехов в переходе на новый стандарт!

ссылка на оригинал статьи http://habrahabr.ru/post/174019/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *