2016-02-12 4 views
0

Пример here для std::forward,Как конструктор перемещения переменной-члена вызывается без использования std :: forward?

// forward example 
#include <utility>  // std::forward 
#include <iostream>  // std::cout 

// function with lvalue and rvalue reference overloads: 
void overloaded (const int& x) {std::cout << "[lvalue]";} 
void overloaded (int&& x) {std::cout << "[rvalue]";} 

// function template taking rvalue reference to deduced type: 
template <class T> void fn (T&& x) { 
    overloaded (x);     // always an lvalue 
    overloaded (std::forward<T>(x)); // rvalue if argument is rvalue 
} 

int main() { 
    int a; 

    std::cout << "calling fn with lvalue: "; 
    fn (a); 
    std::cout << '\n'; 

    std::cout << "calling fn with rvalue: "; 
    fn (0); 
    std::cout << '\n'; 

    return 0; 
} 

Output: 
calling fn with lvalue: [lvalue][lvalue] 
calling fn with rvalue: [lvalue][rvalue] 

упоминает, что

тот факт, что все названные значения (такие как параметры функции) всегда оценить, как lvalues ​​(даже тех, которые объявлены как ссылки RValue)

Принимая во внимание, что типичный конструктор перемещения выглядит как

ClassName(ClassName&& other) 
    : _data(other._data) 
{ 
} 

, который выглядит как _data(other._data), должен ссылаться на конструктор перемещения класса _data. Но как это возможно без использования std::forward? Другими словами, не должно быть

ClassName(ClassName&& other) 
    : _data(std::forward(other._data)) 
{ 
} 

?

Потому что, как указано в станд: вперед случае,

все то названные значения должны оценить, как именующее

Я все больше и больше, как C++ из-за глубины вопроса, как это и тот факт, что язык достаточно смелый, чтобы обеспечить такие возможности :) Спасибо!

+2

Вы должны использовать 'std :: move' в конструкторе. см.: [Как определить конструктор перемещения?] (http://stackoverflow.com/questions/9456910/howto-define-a-move-constructor) – NathanOliver

+2

_ «В то время как типичный конструктор перемещения выглядит ...» _ Нет, нет. Кроме того, 'std :: forward' предназначен для _forwarding_ вещей как их исходной категории значений, либо rvalue, либо lvalue, в зависимости от аргумента шаблона, используемого с' std :: forward'. Просто сказать 'std :: forward (other._data)' без списка аргументов шаблона даже не будет компилироваться. Если вы просто хотите rvalue, используйте 'std :: move' не' std :: forward'. –

+0

@JonathanWakely Но если аргумент lvalue ClassName (ClassName && other) даже не будет вызван, потому что прототип функции не сопоставляется. – user557583

ответ

3

Типичный конструктор шага выглядит следующим образом (предполагается, что он явно внедрен: вы можете предпочесть = default):

ClassName::ClassName(ClassName&& other) 
    : _data(std::move(other._data)) { 
} 

Без std::move() члена копируются: поскольку он имеет название other является именующим , Объект, связанный с ссылкой, является rvalue или объектом, рассматриваемым как таковой.

std::forward<T>(obj)всегда используется с явным аргументом шаблона. На практике это тип, который выводится для справочника . Они выглядят замечательно, как ссылки rvalue, но что-то полностью разные! В частности, ссылка на пересылку может относиться к значению l.

Возможно, вас заинтересовала статья Two Daemons, в которой подробно описывается разница.

+0

Я вижу. Хм, на самом деле я взял фрагмент конструктора перемещения (неправильный?) С страницы msdn: https://msdn.microsoft.com/en-us/library/dd293665.aspx. Неужели MS сделала ошибку? – user557583

+0

@ user557583: на странице нет конструктора перемещения, показывающего, как перемещать элемент, который сам имеет конструктор перемещения. Он показывает недобросовестные реализации оператора присваивания копий (он не исключает исключения, поскольку распределение может выдавать, что приводит к двойному удалению) и перемещают конструктор (нет смысла сначала инициализировать элементы в списке инициализаторов и затем немедленно установите их в тело). «Надежное программирование» также не рекомендуется, так как оно создает накладные расходы (проверка самозапуска и ненужная инициализация и удаление). Это очень плохая страница! –

+0

Kuhl Я вижу. Я просто удивлен, что официальное руководство по кодированию Microsoft вводит в заблуждение. – user557583

1

Этот Ideone example должен сделать все для вас довольно понятным. Если нет, продолжайте читать.

Следующий конструктор принимает только Rvalues. Однако, поскольку аргумент «другой» получил имя, он потерял свою «rvalueness» и теперь является Lvalue. Чтобы вернуть его в Rvalue, вы должны использовать std::move. Здесь нет причин использовать std::forward, потому что этот конструктор не принимает Lvalues. Если вы попытаетесь вызвать его с помощью Lvalue, вы получите ошибку компиляции.

ClassName(ClassName&& other) 
    : _data(std::move>(other._data)) 
{ 
    // If you don't use move, you could have cout << other._data; 
    // And you will notice "other" has not been moved.  
} 

Следующий конструктор принимает как Lvalues, так и Rvalues.Скотт Мейерс назвал его «Universal Rerefences», но теперь он называется «Forwarding References». Вот почему здесь нужно использовать std::forward, так что если бы другой был Rvalue, конструктор _data будет вызван с Rvalue. Если другое было Lvalue, _data будет построено с Lvalue. Вот почему это называется perfect-forwarding.

template<typename T> 
ClassName(T&& other) 
    : _data(std::forward<decltype(_data)>(other._data)) 
{ 
} 

Я попытался использовать ваши конструктор в качестве примера, чтобы вы могли понять, но это не относится к конструкторам. Это относится и к функциям.

Вит первого примера tho, так как ваш первый конструктор принимает только Rvalues, вы можете использовать std::forward вместо этого, и оба будут делать то же самое. Но лучше не делать этого, потому что люди могут подумать, что ваш конструктор принимает forwarding reference, когда он на самом деле этого не делает.

+1

'std :: forward (other._data)' не будет компилироваться, поэтому вы не сможете его использовать –

+0

Он компилируется. http://ideone.com/ZELjh3 – Jts

+0

@JonathanWakely 'std :: forward ' это то же самое, что 'std :: move', если' T' не поступает из ссылки пересылки –

1

std::forward следует использовать с forwarding reference.
std::move следует использовать с rvalue reference.

Нет ничего особенного в конструкторах. Правила применяются одинаково для любой функции, функции-члена или конструктора.

Главное, чтобы понять, когда у вас есть forwarding reference и когда у вас есть rvalue reference. Они похожи, но нет.

Ссылка переадресации всегда в виде:

T&& ref

для T некоторых выведенного типа.

Например, это ссылка переадресации:

template <class T> 
auto foo(T&& ref) -> void; 

Все эти RValue ссылки:

auto foo(int&& ref) -> void; // int not deduced 

template <class T> 
auto foo(const T&& ref); // not in form `T&&` (note the const) 

template <class T> 
auto foo(std::vector<T>&& ref) -> void; // not in form `T&&` 

template <class T> 
struct X { 
    auto foo(T&& ref) -> T; // T not deduced. (It was deduced at class level) 
}; 

Более пожалуйста, проверьте this excellent in-depth article by Scott Meyers с припиской, что, когда статья была написана термин " универсальная ссылка "(фактически введенный самим Скоттом). Теперь было решено, что «ссылка на пересылку» лучше описывает ее назначение и использование.


Так что ваш пример должен быть:

ClassName(ClassName&& other) 
    : _data(std::move(other._data)) 
{ 
} 

в other является rvalue reference потому ClassName не выведенный типа.

+0

Ссылка на статью Скотта мертва или просто неверна. Пожалуйста, проверьте, что – Patryk

+0

@Patryk ty, не знаю, что произошло. Я сейчас установил правильную ссылку. – bolov

+0

Я лично считаю, что универсальная ссылка, имеющая точный параметр типа T &&, слишком тонкая и, следовательно, неуловима. Хорошо, что я это понимаю сейчас. – user557583