Я пытаюсь получить rvalue
Экземпляр этого класса:
#include <iostream>
#define msg(x) std::cout << x " constructor\n"
struct X {
int i;
X(int i) : i(i) {msg("X");}
X(const X& x) : i(x.i) {std::cout << "X copy\n";}
X(X&& x) {std::swap(i, x.i); std::cout << "X move\n";}
};
в переменную экземпляра x
этого класса:
struct A {
X x;
A(X x) : x(x) {msg("A");}
};
вот так:
int main() {
A a(X(1));
std::cout << a.x.i << "\n\n";
}
без каких-либо копий или движений.
Согласно этим ссылкам,
и много много постов на SO (поэтому, пожалуйста, прочитайте до конца, прежде чем пометить как дубликат), Я должен положиться на копия elision, чьи условия должны быть выполнены, если я передать по значению. Обратите внимание, что есть два требуется копия разрешения, а именно:
constructor call -> constructor local variable -> instance variable
как видно при отключении копирования (скомпилируйте с g++-4.8 -std=c++11 -fno-elide-constructors
):
X constructor
X move
X copy
A constructor
1
Так что есть один move
шаг и один copy
шаг, который должен исчезнуть, если я включу функцию elision (компилировать с g++-4.8 -std=c++11 -O3
):
X constructor
X copy
A constructor
1
Облом, copy
шаг остался!
Могу ли я стать лучше с любым другим вариантом std::move()
, std::forward
или проходя как rvalue-reference
?
struct B {
X x;
B(X x) : x(std::move(x)) {msg("B");}
};
struct C {
X x;
C(X x) : x(std::forward<X>(x)) {msg("C");}
};
struct D {
X x;
D(X&& x) : x(std::move(x)) {msg("D");}
};
int main() {
B b(X(2));
std::cout << b.x.i << "\n\n";
C c(X(3));
std::cout << c.x.i << "\n\n";
D d(X(4));
std::cout << d.x.i << "\n\n";
}
который производит вывод:
X constructor
X move
B constructor
2
X constructor
X move
C constructor
3
X constructor
X move
D constructor
4
Хорошо, я повернул copy
в move
, но это не удовлетворительно!
Далее я попытался сделать экземпляр переменной x
ссылка X&
:
struct E {
X& x;
E(X x) : x(x) {msg("E");}
};
int main() {
E e(X(5));
std::cout << e.x.i << "\n\n";
}
который производит:
X constructor
E constructor
1690870696
Плохая идея! Я избавился от move
но rvalue
пример того, что x
ссылался на то, что меня уничтожили под моим креслом, поэтому последняя строка печатает мусор вместо 5
, Две заметки:
g++-4.8
не предупредил меня ни о чем, даже с -pedantic -Wall -Wextra
5
при компиляции с -O0
Так что эта ошибка может остаться незамеченной на долгое время!
Так это безнадежный случай? Ну нет:
struct F {
X& x;
F(X& x) : x(x) {msg("F");}
};
int main() {
X x(6);
F f(x);
std::cout << f.x.i << "\n";
}
печатает:
X constructor
F constructor
6
В самом деле? Не фантазии нового C++11
функции, без права копирования по усмотрению компилятора, просто старый способ передачи по ссылке в стиле FORTRAN66 выполняет то, что я хочу и, вероятно, будет работать лучше?
Итак, вот мои вопросы:
rvalues
? Я пропустил какие-либо функции?lvalue
-ссылка действительно лучшая, или есть скрытые расходы в X x(6)
шаг?x
жить после строительства f
?lvalue
ссылка на внешний экземпляр? Не вдаваясь в подробности вашего вопроса, копирование elision в основном используется как можно больше. Вот короткая демонстрация:
#include <iostream>
#include <utility>
struct X
{
int n_;
explicit X(int n) : n_(n) { std::cout << "Construct: " << n_ << "\n"; }
X(X const & rhs) : n_(rhs.n_) { std::cout << "X copy:" << n_ << "\n"; }
X(X && rhs) : n_(rhs.n_) { rhs.n_ = -1; std::cout << "X move:" << n_ << "\n"; }
~X() { std::cout << "Destroy: " << n_ << "\n"; }
};
struct A
{
explicit A(X x) : x_(std::move(x)) {};
X x_;
};
struct B
{
X x;
};
int main()
{
A a(X(12));
B b { X(24) };
}
это производит:
Construct: 12
X move:12
Destroy: -1
Construct: 24
Destroy: 24
Destroy: 12
Один двигаться в x_(std::move(x))
не допускается, поскольку не требует возврата функции. Но это все равно хорошо. И обратите внимание, как совокупность b
действительно инициализируется «на месте».
Ваш пример F
показывает, что вы готовы раскрыть связь X
к его окружающему классу. В этом случае вы можете сделать специальный конструктор для A
который строит X
непосредственно:
struct A
{
explicit A(X x) : x_(std::move(x)) {};
X x_;
// Better approach
struct direct_x_t {};
static direct_x_t direct_x;
// In our case, this suffices:
A(direct_x_t, int n) : x_(n) {}
// Generally, a template may be better: (TODO: add SFINAE control)
template <typename ...Args> A(direct_x_t, Args &&... args)
: x_(std::forward<Args>(args)...) {}
};
Использование:
A a(A::direct_x, 36);