C ++ 11 перемещать sematincs, я понимаю это право? [Дубликат]

Вы можете использовать эту пользовательскую библиотеку (написанную с помощью Promise) для выполнения удаленного вызова.

function $http(apiConfig) {
    return new Promise(function (resolve, reject) {
        var client = new XMLHttpRequest();
        client.open(apiConfig.method, apiConfig.url);
        client.send();
        client.onload = function () {
            if (this.status >= 200 && this.status < 300) {
                // Performs the function "resolve" when this.status is equal to 2xx.
                // Your logic here.
                resolve(this.response);
            }
            else {
                // Performs the function "reject" when this.status is different than 2xx.
                reject(this.statusText);
            }
        };
        client.onerror = function () {
            reject(this.statusText);
        };
    });
}

Пример простого использования:

$http({
    method: 'get',
    url: 'google.com'
}).then(function(response) {
    console.log(response);
}, function(error) {
    console.log(error)
});
1395
задан RJFalconer 2 April 2015 в 09:14
поделиться

11 ответов

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

#include <cstring>
#include <algorithm>

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.

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

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

2062
ответ дан Community 1 September 2018 в 08:02
поделиться

Чтобы проиллюстрировать необходимость в семантике перемещения , рассмотрим этот пример без семантики перемещения:

Вот функция, которая берет объект типа T и возвращает объект тот же тип T:

T f(T o) { return o; }
  //^^^ new object constructed

Вышеупомянутая функция использует вызов по значению , что означает, что когда эта функция вызывается, объект должен быть построен для использования этой функцией. Поскольку функция также возвращает значение , для возвращаемого значения создается другой новый объект:

T b = f(a);
  //^ new object constructed

Были построены два новых объекта, один из которых является временным объектом, который используется только для продолжительности функции.

Когда новый объект создается из возвращаемого значения, конструктор копирования вызывается в copy содержимое временного объекта для нового объект b. После завершения функции временный объект, используемый в этой функции, выходит из области видимости и уничтожается.


Теперь давайте рассмотрим, что делает конструктор копии .

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

// Copy constructor
T::T(T &old) {
    copy_data(m_a, old.m_a);
    copy_data(m_b, old.m_b);
    copy_data(m_c, old.m_c);
}

С семантикой перемещения теперь можно сделать большую часть этой работы менее неприятной, просто переместить данные, а не копировать.

// Move constructor
T::T(T &&old) noexcept {
    m_a = std::move(old.m_a);
    m_b = std::move(old.m_b);
    m_c = std::move(old.m_c);
}

Перемещение данных предполагает повторное связывание данных с новым объектом , И копия вообще не выполняется .

Это выполняется с помощью ссылки rvalue. Ссылка rvalue очень похожа на ссылку lvalue с одним важным отличием: ссылка rvalue может быть перемещена , а значение lvalue не может быть.

Из cppreference.com :

Чтобы обеспечить надежную гарантию исключения, пользовательские конструкторы перемещения не должны генерировать исключения. На самом деле стандартные контейнеры обычно полагаются на std :: move_if_noexcept, чтобы выбирать между перемещением и копированием, когда элементы контейнера необходимо переместить. Если предусмотрены как конструкторы копирования, так и перемещения, разрешение перегрузки выбирает конструктор перемещения, если аргумент представляет собой rvalue (либо значение praleue, такое как безымянное временное, либо значение x, такое как результат std :: move), и выбирает конструктор копирования, если аргумент - это lvalue (именованный объект или оператор / оператор, возвращающий ссылку lvalue). Если предоставлен только конструктор копирования, все его категории выбирают (до тех пор, пока он принимает ссылку на const, поскольку rvalues ​​может связывать ссылки const), что делает копирование резервной копии для перемещения, когда перемещение недоступно. Во многих ситуациях перемещение конструкторов оптимизируется, даже если они будут производить наблюдаемые побочные эффекты, см. Копирование elision. Конструктор называется «конструктором перемещения», когда он принимает значение rvalue в качестве параметра. Он не обязан ничего перемещать, класс не требует, чтобы ресурс был перемещен, а «конструктор перемещения» не мог перемещать ресурс, как в допустимом (но, возможно, не разумном) случае, когда параметр является const rvalue reference (const T & amp;).

6
ответ дан Andreas DM 1 September 2018 в 08:02
поделиться

Я пишу это, чтобы убедиться, что это правильно.

Перенос семантики был создан, чтобы избежать ненужного копирования больших объектов. Bjarne Stroustrup в своей книге «Язык программирования C ++» использует два примера, когда по умолчанию происходит ненужное копирование: одно, замена двух больших объектов и два - возврат большого объекта из метода.

Обмен двумя большими объектами обычно включает в себя копирование первого объекта во временный объект, копирование второго объекта на первый объект и копирование временного объекта во второй объект. Для встроенного типа это очень быстро, но для больших объектов эти три копии могут занимать много времени. «Назначение перемещения» позволяет программисту переопределить поведение копии по умолчанию и вместо этого заменять ссылки на объекты, а это значит, что копирования вообще нет, а операция свопинга выполняется намного быстрее. Назначение перемещения может быть вызвано вызовом метода std :: move ().

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

В языках, которые не позволяют создавать локальные объекты (то есть объекты в стеке), эти типы проблем не возникают, поскольку все объекты выделены в куче и являются всегда доступен по ссылке.

4
ответ дан Chris B 1 September 2018 в 08:02
поделиться

Перемещение семантики - это передача ресурсов, а не копирование их, когда больше не требуется исходное значение.

В C ++ 03 объекты часто копируются, только для их уничтожения или назначения перед любым кодом снова использует значение. Например, когда вы возвращаетесь по значению из функции, если только RVO не запускается - возвращаемое значение копируется в стек стека вызывающего, а затем выходит за пределы области действия и уничтожается. Это всего лишь один из многих примеров: см. Pass-by-value, когда исходный объект является временным, такие алгоритмы, как sort, которые просто переупорядочивают элементы, перераспределение в vector при превышении capacity() и т. Д.

Когда такие пары «копировать / уничтожать» дороги, это обычно потому, что объекту принадлежит какой-то тяжеловесный ресурс. Например, vector<string> может владеть динамически выделенным блоком памяти, содержащим массив объектов string, каждый со своей собственной динамической памятью. Копирование такого объекта является дорогостоящим: вам необходимо выделить новую память для каждого динамически выделенного блока в источнике и скопировать все значения в. Затем вам нужно освободить всю память, которую вы только что скопировали. Однако перемещение большого vector<string> означает просто копирование нескольких указателей (которые относятся к блоку динамической памяти) к месту назначения и обнуление их в источнике.

22
ответ дан Dave Abrahams 1 September 2018 в 08:02
поделиться
898
ответ дан frslm 1 September 2018 в 08:02
поделиться

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

17
ответ дан gd1 1 September 2018 в 08:02
поделиться

Предположим, что у вас есть функция, которая возвращает существенный объект:

Matrix multiply(const Matrix &a, const Matrix &b);

Когда вы пишете такой код:

Matrix r = multiply(a, b);

, тогда обычный компилятор C ++ создаст временную объект для результата multiply(), вызовите конструктор копирования для инициализации r, а затем уничтожьте временное возвращаемое значение. Перемещение семантики в C ++ 0x позволяет вызвать «move constructor» для инициализации r путем копирования его содержимого, а затем отменить временное значение без его разрушения.

Это особенно важно, если (как, например, пример Matrix выше), копируемый объект выделяет дополнительную память в куче для хранения своего внутреннего представления. Конструктор копирования должен либо сделать полную копию внутреннего представления, либо использовать семантику ссылок и семантику копирования на запись. Конструктор перемещения оставил бы кучную память и просто скопировал указатель внутри объекта Matrix.

49
ответ дан Greg Hewgill 1 September 2018 в 08:02
поделиться

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

Он очень доступен и легко читается, и он отлично подходит для преимуществ, которые они предлагают. Существуют и другие более свежие и обновленные документы о семантике перемещения, доступные на веб-сайте WG21 , но этот, пожалуй, самый простой, поскольку он приближается к вещам с верхнего уровня и не очень [подробно]

27
ответ дан James McNellis 1 September 2018 в 08:02
поделиться

Семантика перемещения основана на ссылках rvalue . Rvalue - временный объект, который будет уничтожен в конце выражения. В текущем C ++ значения r привязаны только к const ссылкам. C ++ 1x позволит ссылаться на ссылки не на const rvalue, записанные T&&, которые являются ссылками на объекты rvalue. Так как rvalue будет умирать в конце выражения, вы можете украсть его данные . Вместо того, чтобы копировать в другой объект, вы переместите свои данные в него.

class X {
public: 
  X(X&& rhs) // ctor taking an rvalue reference, so-called move-ctor
    : data_()
  {
     // since 'x' is an rvalue object, we can steal its data
     this->swap(std::move(rhs));
     // this will leave rhs with the empty data
  }
  void swap(X&& rhs);
  // ... 
};

// ...

X f();

X x = f(); // f() returns result as rvalue, so this calls move-ctor

В приведенном выше коде со старыми компиляторами результат f() был скопирован в x с помощью конструктора копий X. Если ваш компилятор поддерживает перемещение семантики, а X имеет конструктор move, то вместо этого вызывается. Поскольку аргумент rhs является rvalue , мы знаем, что он больше не нужен, и мы можем украсть его значение. Таким образом, значение перемещено из неназванного временного объекта, возвращенного с f() в x (в то время как данные x, инициализированные до пустого X, перемещаются во временное, что будет уничтожить после назначения).

69
ответ дан sbi 1 September 2018 в 08:02
поделиться

В простых (практических) терминах:

Копирование объекта означает копирование его «статических» членов и вызов оператора new для его динамических объектов. Правильно?

class A
{
   int i, *p;

public:
   A(const A& a) : i(a.i), p(new int(*a.p)) {}
   ~A() { delete p; }
};

Однако для перемещения объекта (повторяю, с практической точки зрения) подразумевается только копирование указателей на динамические объекты, а не создание новых.

Но разве это не опасно? Конечно, вы можете разрушить динамический объект дважды (ошибка сегментации). Поэтому, чтобы этого избежать, вы должны «недействить» указатели источника, чтобы избежать их разрушения дважды:

class A
{
   int i, *p;

public:
   // Movement of an object inside a copy constructor.
   A(const A& a) : i(a.i), p(a.p)
   {
     a.p = nullptr; // pointer invalidated.
   }

   ~A() { delete p; }
   // Deleting NULL, 0 or nullptr (address 0x0) is safe. 
};

Хорошо, но если я перемещаю объект, исходный объект становится бесполезным, нет? Конечно, но в некоторых ситуациях это очень полезно. Наиболее очевидным является то, что я вызываю функцию с анонимным объектом (временным, rvalue объектом, ..., вы можете называть его разными именами):

void heavyFunction(HeavyType());

В этой ситуации анонимный объект , затем копируется в параметр функции, а затем удаляется. Итак, здесь лучше перемещать объект, потому что вам не нужен анонимный объект, и вы можете сэкономить время и память.

Это приводит к концепции ссылки «rvalue». Они существуют в C ++ 11 только для того, чтобы определить, является ли полученный объект анонимным или нет. Я думаю, вы уже знаете, что «lvalue» является назначаемым объектом (левая часть оператора =), поэтому вам нужна именованная ссылка на объект, способный действовать как lvalue. Rvalue - это точно противоположное, объект без названных ссылок. Из-за этого анонимный объект и rvalue являются синонимами. Итак:

class A
{
   int i, *p;

public:
   // Copy
   A(const A& a) : i(a.i), p(new int(*a.p)) {}

   // Movement (&& means "rvalue reference to")
   A(A&& a) : i(a.i), p(a.p)
   {
      a.p = nullptr;
   }

   ~A() { delete p; }
};

В этом случае, когда объект типа A должен быть «скопирован», компилятор создает ссылку на lvalue или ссылку на rvalue в соответствии с именем или именем переданного объекта , Когда нет, ваш конструктор move вызывается, и вы знаете, что объект является временным, и вы можете перемещать его динамические объекты, а не копировать их, экономя пространство и память.

Важно помнить, что «статические» объекты всегда копируются. Невозможно «переместить» статический объект (объект в стеке, а не в кучу). Таким образом, различие «move» / «copy», когда объект не имеет динамических членов (прямо или косвенно), не имеет значения.

Если ваш объект является сложным, а деструктор имеет другие вторичные эффекты, например, вызов функция библиотеки, вызов других глобальных функций или что бы то ни было, возможно, лучше сигнализировать движение с флагом:

class Heavy
{
   bool b_moved;
   // staff

public:
   A(const A& a) { /* definition */ }
   A(A&& a) : // initialization list
   {
      a.b_moved = true;
   }

   ~A() { if (!b_moved) /* destruct object */ }
};

Итак, ваш код короче (вам не нужно делать nullptr для каждого динамического элемента) и более общий.

Другой типичный вопрос: в чем разница между A&& и const A&&? Конечно, в первом случае вы можете изменить объект, а во втором - нет, но практический смысл? Во втором случае вы не можете его модифицировать, поэтому у вас нет способов сделать недействительным объект (кроме как с изменяемым флагом или что-то в этом роде), и нет никакой практической разницы с конструктором копирования.

И что отличное переадресация? Важно знать, что «ссылка на rvalue» является ссылкой на именованный объект в «области вызова». Но в фактическом объеме ссылка rvalue является именем объекта, поэтому он действует как именованный объект. Если вы передаете ссылку rvalue на другую функцию, вы передаете именованный объект, поэтому объект не принимается как временный объект.

void some_function(A&& a)
{
   other_function(a);
}

Объект a будет скопирован в фактический параметр other_function. Если вы хотите, чтобы объект a продолжал обрабатываться как временный объект, вы должны использовать функцию std::move:

other_function(std::move(a));

С помощью этой строки std::move передаст a значение rvalue и other_function получит объект как неназванный объект. Конечно, если other_function не имеет специфической перегрузки для работы с неназванными объектами, это различие не имеет значения.

Это идеальная пересылка? Нет, но мы очень близки. Идеальная пересылка полезна только для работы с шаблонами, с целью сказать: если мне нужно передать объект другой функции, мне нужно, чтобы, если я получаю именованный объект, объект передается как именованный объект, а когда нет, Я хочу передать его как неназванный объект:

template<typename T>
void some_function(T&& a)
{
   other_function(std::forward<T>(a));
}

Это подпись прототипической функции, которая использует совершенную пересылку, реализованную на C ++ 11 с помощью std::forward. Эта функция использует некоторые правила создания экземпляра шаблона:

 `A& && == A&`
 `A&& && == A&&`

Итак, если T является ссылкой lvalue на A (T = A & amp;), a также (A & amp; & amp; & amp; & amp; ; => A & amp;). Если T является ссылкой rvalue на A, a также (A & amp; & amp; & amp; => A & amp; & amp;). В обоих случаях a является именованным объектом в действительной области, но T содержит информацию о его «ссылочном типе» с точки зрения области вызова. Эта информация (T) передается как параметр шаблона в forward, а «a» перемещается или нет в соответствии с типом T.

19
ответ дан simbo1905 1 September 2018 в 08:02
поделиться

Вы знаете, что означает семантика копии? это означает, что у вас есть типы, которые можно копировать, для определяемых пользователем типов, которые вы определяете это, либо купите явно написанную конструктор копирования & amp; оператор присваивания или компилятор генерирует их неявно. Это будет делать копия.

Семантика перемещения - это, в основном, определяемый пользователем тип с конструктором, который принимает ссылку на r-значение (новый тип ссылки с использованием & amp; & amp; (yes two ampersands)), который не является -const, это называется конструктором перемещения, то же самое относится к оператору присваивания. Итак, что делает конструктор перемещения, а вместо того, чтобы копировать память из его исходного аргумента, он «перемещает» память из источника в пункт назначения.

Когда вы захотите это сделать? well std :: vector - пример, предположим, что вы создали временный std :: vector, и вы возвращаете его из функции say:

std::vector<foo> get_foos();

У вас возникнут накладные расходы из конструктора копирования, когда функция возвращает, если (и она будет в C ++ 0x) std :: vector имеет конструктор перемещения вместо копирования, он может просто установить его указатели и «переместить» динамически выделенную память в новый экземпляр. Это похоже на семантику передачи права собственности на std :: auto_ptr.

13
ответ дан snk_kid 1 September 2018 в 08:02
поделиться
Другие вопросы по тегам:

Похожие вопросы: