Двухэтапное копирование для получения значения rvalue в вызове конструктора в качестве переменной экземпляра

Я пытаюсь получить 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 ссылка на внешний экземпляр?

0

Решение

Не вдаваясь в подробности вашего вопроса, копирование 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);
2

Другие решения


По вопросам рекламы ammmcru@yandex.ru
Adblock
detector