Пополняем шпаргалки по C++: неявно-генерируемые перемещающий конструктор и оператор присваивания

  • Tutorial
Когда не так часто, как хотелось бы, приходится работать с языком, некоторые аспекты забываются. А некоторые никогда и не откладываются в голове. Поэтому, когда возникают вопросы, приходится отвлекаться и лезть в документацию.

Чтобы сэкономить время в последующем, а также, чтобы лучше понять в ходе обучения, крайне помогает вести конспекты и делать наглядные шпаргалки. Шпаргалку можно повесить рядом на стену. Хороши шпаргалки в виде блок-схем, по которым можно легко, по шагам, получить нужный результат (например выбрать правильный контейнер).

Под катом я решил опубликовать пару шпаргалок для определения условия когда будет создан компилятором неявно-генерируемый перемещающий конструктор и перемещающий оператор присваивания.



Шпаргалки представлены в виде PDF файлов для печати на принтере A4, в виде картинки PNG, а также исходников в SVG.

Неявно-генерируемый перемещающий конструктор




Неявно-генерируемый перемещающий оператор присваивания



Исходники и PDF формата A4: yadi.sk/d/s7t7uEdKAxHKq/Cheatsheets/My/C%2B%2B-move-ctor

TriviallyCopyable концепт
TriviallyCopyable концепт


На диаграммах есть отсылки к тривиальности конструкторов и т.п. Используя en.cppreference.com/w/cpp/concept/TriviallyCopyable составил карту памяти:


Исходники и PDF: yadi.sk/d/s7t7uEdKAxHKq/Cheatsheets/My/C%2B%2B-move-ctor/Concepts


Вот и всё, жду замечаний. Если кому-то пригодится, не забывайте выражать свои эмоции в комментариях.

Используемые материалы:
en.cppreference.com/w/cpp/language/move_operator
en.cppreference.com/w/cpp/language/move_constructor
en.cppreference.com/w/cpp/language/copy_constructor
en.cppreference.com/w/cpp/language/as_operator
en.cppreference.com/w/cpp/language/destructor
en.cppreference.com/w/cpp/concept/TriviallyCopyable

PS где принято хранить файлы (не картинки) для хабра?
Share post

Similar posts

Comments 19

    +1
    Ох я недавно намучился с GNU compiler 4.8, Boost 4.6 и с++11. Веселья было тьма. В версии 4.6 буста, считается, что если создать свой move конструктор, то конструктор копирования все равно будет создан, ан-нет, g++ 4.8 начинает усиленно ругаться, что, мол, в файле boost::shared_pointer.cpp вызывается функция с атрибутом deleted. Такие дела. Так что статья важная.
      +4
      boost 4.6? Вы подразумевали 1.46? Если да, то почему такой старый? Если нет, то что за буст вы имели в виду?
        +1
        Извините, конечно 1.46. Наименование версий в моей голове заклинило, а за то, что я не проверяю все перед тем как писать комментарий — гореть мне в аду.

        Boost 1.46 уже старый, но еще не очень старый, он все еще дефолтный, например в Ubuntu 12.04 (хотя возможность обновиться есть). Проблема в том, что есть некий большой софт, который зависит от Boost 1.46, потому приходится мучаться.

        До версии 1.53 описанный мной баг присутствует. Если вы попробуете собрать любой проект с g++4.8 (другие версии g++ не проверял, но должно репродьюситься на всех начиная с 4.7, а может и каких-то вариаций 4.6, зависит от степени поддержки с++11), который использует shared pointers из Boost версии < 1.53, вы получите довольно зубодробительные ошибки в шаблонах. На убунте 12.04 на стоковом g++4.6 не репродьюсится.

        Чтобы поделиться еще немного своей болью скажу, что g++4.6, с которым все прекрасно собирается знать не знает о том, какие инструкции оптимизации нужно генерить для интеловских процессоров, построенных по технологии Haswell… Вот такая беда. :)
          0
          Debian Wheezy, Boost 1.49. Мне удается скомпилировать следующий код версиями gcc 4.4, 4.6, 4.7, 4.8, 4.9 (из Debian Wheezy и Jessie) как с опцией -std=c++11 / -std=c++0x так и без неё. Подозреваю, что на Squeeze с его Boost 1.42 тоже всё будет компилироваться.

          #include <string>
          #include <boost/shared_ptr.hpp>
          
          typedef boost::shared_ptr<std::string> StringPtr;
          
          StringPtr f() {
          	return StringPtr(new std::string);
          }
          
          int main() {
          	StringPtr ptr1(new std::string);
          	StringPtr ptr2 = ptr1;
          	StringPtr ptr3 = f();
          }
          


          Кстати, что за boost::shared_pointer.cpp? Насколько я помню, shared_pointer реализован как header-only.
            0
            Хм, удивительное рядом. Возможно, я ошибся в причине такого поведения у меня. Я завтра доберусь до рабочего компа и отпишусь когда разберусь что происходит. И да, я не знаю какой черт меня дернул добавить cpp в конце. Читая свой первый комментарий, я не могу понять в каком состоянии я его писал.

            Спасибо за пример. По крайней мере он показывает, что я точно не прав. Осталось узнать насколько и конкретно в чем.
              0
              Вот тут есть вопрос на StackOverflow по той же тематике. Я практически уверен, что с boost 1.42 ваш код не скомпилируется. Однако это означает, что в 1.49 уже все исправлено.
                +1
                Установил Debian Squeeze в виртуалку, поставил туда Boost 1.42 и gcc 4.4. Компилируется со стандартными опциями и с -std=c++0x. Установил туда gcc-4.9 из Debian Jessie. Попробовал скомпилировать c -std=c++11, вот тут действительно получилась ошибка. А без -std=c++11 всё компилируется.

                Кстати, clang 3.4 из Debian Jessie успешно компилирует этот код против Boost 1.42 как с опцией -std=c++11, так и без неё. Возможно, clang более либерален к коду в некотором смысле. Например, конструирование std::ifstream из std::string в clang компилируется, а в gcc нет. Хорошо это или плохо, я не знаю.
                  0
                  И не лень же вам этим в воскресенье заниматься :) Да, именно так, как я и говорил. Новый gcc + старый boost + с++11 = головная боль. А на clang давно хочу посмотреть, но что-то руки никак не доходят.
              0
              Да не переживайте Вы так, я сам с gcc 3.4.6 и бустом 1.49 застрял похоже надолго (mmu-less armv4 железка, похоже больше ничего на ней и не взлетит).
          0
          Всё-таки это неявно генерируемый конструктор, а не неявный конструктор.
            0
            Согласен, было огромное желание подсократить количество текста в ячейках. Я подумаю как лучше представить, или просто небольшую легенду с допущениями или всё же полный вариант. С другой стороны некий каламбур получается, если заменить синонимами: Неявно генерируемый конструктор не генерируется. В любом случае — я вас услышал.
              0
              По тексту исправился. На картинках пока решил не трогать. Если кто хочет — SVG есть, в Inkscape на ура правится.
            +1
            Я все возвращаюсь к вашей статье. Возможно, это только мое мнение, но я бы убрал этот градиент с ячеек и просто сделал бы их однотонными в мягких тонах. Градиент выглядит как арт в office xp…
              0
              Так можно просто другую копию сделать. Проблем нет. Попробуйте глянуть c++11-move-assignment-solid.svg что по ссылке на исходники. Если понравится, можно скорректировать и второй и пусть будет на любителя.
              0
              Я запутался.
              Есть класс (структура, не важно):
              struct Test
              {
              	Test() : s("s") {}
              	Test(const Test& other) : s(other.s) { puts("Test(const Test& other)"); }
              	std::string s;
              };
              

              В этом случае неявный move-constructor не создаётся (По схеме: дважды Нет).
              Должен ли компилироваться следующий код?
              int main()
              {
              	Test t1{};
              	Test t2(std::move(t1));
              
              	puts(t1.s.c_str());
              	puts(t2.s.c_str());
              	return 0;
              }
              

              Ответ — всё в порядке — компилируется и вызывается копирующий конструктор:
              Test(const Test& other)
              s
              s
              

              Почему? Я как-то не правильно понимаю когда вызывается copy ctor? (Copy constructors):
              The copy constructor is called whenever an object is initialized from another object of the same type, which includes

              initialization, T a = b; or T a(b);, where b is of type T

              function argument passing: f(a);, where a is of type T and f is void f(T t)

              function return: return a; inside a function such as T f(), where a is of type T, which has no move constructor.


              Если же явно попросить компилятор сгенерировать move-конструктор, то уже действительно вызывается move-конструктор:
              struct Test
              {
              	Test() : s("s") {}
              	Test(const Test& other) : s(other.s) { puts("Test(const Test& other)"); }
              	Test(Test&& other) = default;
              	std::string s;
              };
              //...
              
              s
              

              Если же явно запретить компилятору генерировать move-конструктор:
              struct Test
              {
              	Test() : s("s") {}
              	Test(const Test& other) : s(other.s) { puts("Test(const Test& other)"); }
              	Test(Test&& other) = delete;
              	std::string s;
              };
              

              Тогда уже компилятор ругается!
              Что получается:
              Test t2(std::move(t1));
              

              Если move-конструктор неявно НЕ сгенерирован, то вызывается copy-constructor
              Если move-конструктор явно помечен через delete, то copy-constructor НЕ вызывается

              Я окончательно запутался. Пошёл смотреть стандарт :(
                0
                Попробую на пальцах, при помощи аналогии:
                #include <iostream>
                
                using namespace std;
                
                struct Foo
                {
                    void foo(char a)
                    {
                        cout << (int)a << endl;
                    }
                };
                
                struct Bar
                {
                    void bar(char a)
                    {
                        cout << (int)a << endl;
                    }
                    
                    void bar(int a); // да-да, оно только объхявлено
                };
                
                int main()
                {
                    char a = 100;
                    int  b = 50;
                    
                    Foo foo;
                    Bar bar;
                    
                    foo.foo(a); // ок, печатает 100
                    foo.foo(b); // ок, печатает 50
                    
                    bar.bar(a); // ok, печатает 100
                    bar.bar(b); // ошибка копиляции
                    
                    return 0;
                }
                


                Получается, что в первом случае (Foo) int приведётся к char (без ворнингов только потому, что значение известно компилятору и оно помещается в char). Можешь считать отсутствие метода foo(int) как отсутствие move-ctor при присутсвии copy-ctor. Случай с Bar можно рассматривать как аналогию delete для move-ctor. Конечно, пример натянутый, но вроде понятнее становится.
                  0
                  Я просто думал, что смогу для себя объяснить это в рамках overload resolution (Вот хорошее видео от Stephan T. Lavavej — Overload Resolution).

                  Вроде как даже нашёл: 13.3.1.4 Copy-initialization of class by user-defined conversion (n3797)
                  ...the candidate functions are selected as follows:

                  ...When initializing a temporary to be bound to the first parameter
                  of a constructor that takes a reference to possibly cv-qualified T as its first argument, called with a
                  single argument in the context of direct-initialization of an object of type “cv2 T”, explicit conversion
                  functions are also considered…

                  И 13.3.1 Candidate functions and argument lists:
                  A defaulted move constructor or assignment operator (12.8) that is defined as deleted is excluded from the
                  set of candidate functions in all contexts.

                  Но это не то, скорее всего
                +1
                На ACCU 2014 была хорошая презентация на эту тему:
                Everything You Ever Wanted To Know About Move Semantics. В частности, слайды с 19 по 30.
                  0
                  Кстати да, очень неплохая презентация.

                Only users with full accounts can post comments. Log in, please.