заставить div содержать плавающие дочерние div

Мне легче всего понять семантику переноса с примером кода. Начнем с очень простого строкового класса, который содержит только указатель на блок памяти, выделенный для кучи:

#include 
#include 

class string
{
    char* data;

public:

    string(const char* p)
    {
        size_t size = strlen(p) + 1;
        data = new char[size];
        memcpy(data, p, size);
    }

Поскольку мы решили самостоятельно управлять памятью, нам нужно следовать правилу из трех . Я собираюсь отложить запись оператора присваивания и реализовать только деструктор и конструктор копирования:

    ~string()
    {
        delete[] data;
    }

    string(const string& that)
    {
        size_t size = strlen(that.data) + 1;
        data = new char[size];
        memcpy(data, that.data, size);
    }

Конструктор копирования определяет, что означает копирование строковых объектов. Параметр const string& that связывается со всеми выражениями типа string, которые позволяют делать копии в следующих примерах:

string a(x);                                    // Line 1
string b(x + y);                                // Line 2
string c(some_function_returning_a_string());   // Line 3

Теперь идет ключевое понимание семантики перемещения. Обратите внимание, что только в первой строке, где мы копируем x, эта глубокая копия действительно необходима, потому что позже мы захотим осмотреть x и будем очень удивлены, если x каким-то образом изменилось. Вы заметили, как я только что сказал x три раза (четыре раза, если вы включили это предложение) и означал тот же самый объект каждый раз? Мы называем выражения, такие как x «lvalues».

Аргументы в строках 2 и 3 не являются lvalues, а rvalues, поскольку базовые строковые объекты не имеют имен, поэтому у клиента нет возможности проверить их снова в более поздний момент времени. rvalues ​​обозначают временные объекты, которые уничтожаются на следующей точке с запятой (точнее: в конце полного выражения, которое лексически содержит rvalue). Это важно, потому что во время инициализации b и c мы могли делать все, что хотели, с исходной строкой, а клиент не мог отличить !

C ++ 0x вводит новый механизм, называемый «rvalue reference», который, среди прочего, позволяет нам обнаруживать аргументы значения r через функцию перегрузки. Все, что нам нужно сделать, это написать конструктор с параметром ссылки rvalue. Внутри этого конструктора мы можем сделать все, что хотим с источником, если мы оставим его в правильном состоянии :

    string(string&& that)   // string&& is an rvalue reference to a string
    {
        data = that.data;
        that.data = nullptr;
    }

Что мы здесь сделали? Вместо того, чтобы глубоко копировать данные кучи, мы только что скопировали указатель, а затем установили исходный указатель на нуль. По сути, мы «украли» данные, которые первоначально принадлежали исходной строке. Опять же, ключевое понимание заключается в том, что ни при каких обстоятельствах клиент не мог обнаружить, что источник был изменен. Поскольку мы действительно не делаем копию здесь, мы называем этот конструктор «конструктором перемещения». Его задача - переместить ресурсы с одного объекта на другой, а не копировать их.

Поздравляем, вы теперь понимаете основы семантики перемещения! Давайте продолжим реализацию оператора присваивания. Если вы не знакомы с копией и подкачкой idiom , изучите ее и вернитесь, потому что это потрясающая идиома C ++, связанная с безопасностью исключений.

    string& operator=(string that)
    {
        std::swap(data, that.data);
        return *this;
    }
};

Да, это все ? «Где ссылка?» вы можете спросить. «Нам здесь это не нужно!» мой ответ:)

Обратите внимание, что мы передаем параметр that по значению , поэтому that необходимо инициализировать точно так же, как и любой другой строковый объект. Точно как инициализируется that? В старые времена C ++ 98 ответ был бы «с помощью конструктора копирования». В C ++ 0x компилятор выбирает между конструктором копирования и конструктором перемещения на основе того, является ли аргумент оператору присваивания значением l или значением r.

Итак, если вы скажете a = b, copy [] инициализирует that (потому что выражение b является значением lvalue), а оператор присваивания свопит содержимое со свежей созданной глубокой копией. Это само определение идиома копирования и свопа - сделайте копию, замените содержимое копией и затем избавьтесь от копии, оставив область действия. Ничего нового здесь.

Но если вы скажете a = x + y, конструктор перемещения инициализирует that (потому что выражение x + y является rvalue), поэтому нет глубокая копия, только эффективный ход. that по-прежнему является независимым объектом из аргумента, но его конструкция была тривиальной, так как данные кучи не нужно было копировать, просто перемещались. Нет необходимости копировать его, потому что x + y является rvalue, и опять же, можно перейти от строковых объектов, обозначенных rvalues.

Чтобы суммировать, конструктор копирования делает глубокую копию, потому что источник должен оставаться нетронутым. С другой стороны, конструктор перемещения может просто скопировать указатель, а затем установить указатель в источнике на нуль. Это нормально «обнулить» исходный объект таким образом, потому что у клиента нет возможности снова проверить объект.

Надеюсь, что этот пример получил основную точку. Существует гораздо больше ссылок на ссылки и перемещение семантики, которые я намеренно оставил, чтобы это было просто. Если вы хотите получить более подробную информацию, см. мой дополнительный ответ .

13
задан thanksd 1 February 2017 в 03:40
поделиться