std::forward и конструктор копирования

Недавно начал изучать C++. И у меня есть вопрос по std::forward со ссылкой на lvalue и ссылкой на rvalue. Насколько я понимаю, Func(mc) в следующем коде предполагает вызов Func(T& t) из-за правила вывода параметров шаблона. И конструктор копирования MyClass должен вызываться внутри функции с сообщением «конструктор копирования». Однако я не могу получить его, когда запускаю программу. Я проверил, что std::forwad со ссылкой на rvalue и конструктор копирования хорошо работает в других строках. Пожалуйста, помогите мне понять, что происходит в коде. Если я допускаю легкую ошибку или недопонимание, извините, что отнял у вас время. Большое тебе спасибо.

class MyClass {
public:
    MyClass() { printf("constructor.\n"); };
    MyClass(MyClass&) { printf("copy constructor.\n"); };
    MyClass(const MyClass&) { printf("const copy constructor.\n"); };
    MyClass(MyClass&&) { printf("move constructor.\n"); };
    int val = 3;
};

template <typename T>
void Func(T&& t) {
    T new_t_(std::forward<T>(t));
    new_t_.val *= 2;
};

main() {
    MyClass mc;
    Func(mc);  // lvalue <- Func(T&)
    Func(MyClass());  // rsvalue <- Func(T&&)
    printf("mc.val=%d\n", mc.val); // this is for check

    MyClass mc2(mc);  // this is for check of copy constructor
}

Вывод, когда я запускаю программу, следующий:

constructor.
constructor.
move constructor.
mc.val=6
copy constructor.

Я думаю, что между первым и вторым сообщениями «конструктор» должен быть «конструктор копирования».

Большое спасибо еще раз.


person mora    schedule 06.06.2015    source источник
comment
Конструктор MyClass(MyClass&) не очень полезен для многих классов. При использовании шаблона RAII обычно следует только Правило пяти.   -  person dyp    schedule 06.06.2015
comment
Спасибо дип. Я поставил MyClass(MyClass&), чтобы проверить, что этот конструктор копирования не вызывается вместо MyClass(const MyClass&), хотя теперь я знаю, что оба они не вызываются в Func().   -  person mora    schedule 06.06.2015


Ответы (1)


Func(mc);

Этот вызов выведет T как MyClass&. Обратите внимание на ссылку. Это связано с тем, как совершенная переадресация была введена в C++: ссылка на пересылку, такая как T&& параметр функции Func, будет выведена как ссылка lvalue, если соответствующий аргумент функции является lvalue-выражением. ; в противном случае (для xvalues ​​и prvalues) он не будет считаться ссылочным типом.

Затем тип, выведенный для T, будет свернут по ссылке с && из параметра функции T&& t, чтобы сформировать окончательный тип параметра функции. В случае Func(mc), T == MyClass&, поэтому параметр функции становится MyClass& &&, свернутым до MyClass&.

Начиная с T == MyClass&, объявление локальной переменной new_t_ в этом случае будет объявлять ссылку. Новый объект не будет создан:

template <>
void Func<MyClass&>(MyClass& t) {
    MyClass& new_t_(std::forward<MyClass&>(t));
    new_t_.val *= 2;
}

Func(MyClass());

Здесь аргументом функции является выражение-prvalue-выражение MyClass(). T будет выведено в MyClass (без ссылки). Полученный экземпляр шаблона функции выглядит следующим образом:

template <>
void Func<MyClass>(MyClass&& t) {
    MyClass new_t_(std::forward<MyClass>(t));
    new_t_.val *= 2;
}

Если вы хотите скопировать/переместить аргумент функции в локальную переменную, можно использовать метафункцию std::decay или, что более конкретно для проблемы OP, метафункцию std::remove_reference. Например.

template <typename T>
void Func(T&& t) {
    using DT = typename std::decay<T>::type;
    DT new_t_(std::forward<T>(t));
    new_t_.val *= 2;
}
person dyp    schedule 06.06.2015
comment
Большое спасибо, Дип. Это был очень хороший ответ. И, пожалуйста, простите меня за то, что я использую комментарии, чтобы выразить вам свою благодарность. Я новичок в переполнении стека и не знал, что вам ответить. - person mora; 06.06.2015