Когда вызывается вызов Move Constructor?

Я смущен, когда конструктор перемещения получает вызов против конструктора копирования. Я прочитал следующие источники:

Переместить конструктор не вызывается в С++ 0x

Переместить семантику и ссылки rvalue в С++ 11

msdn

Все эти источники либо слишком сложны (я просто хочу простой пример), либо только показать, как писать конструктор перемещения, но не как его называть. Я написал простую проблему, чтобы быть более конкретным:

const class noConstruct{}NoConstruct;
class a
{
private:
    int *Array;
public:
    a();
    a(noConstruct);
    a(const a&);
    a& operator=(const a&);
    a(a&&);
    a& operator=(a&&);
    ~a();
};

a::a()
{
    Array=new int[5]{1,2,3,4,5};
}
a::a(noConstruct Parameter)
{
    Array=nullptr;
}
a::a(const a& Old): Array(Old.Array)
{

}
a& a::operator=(const a&Old)
{
    delete[] Array;
    Array=new int[5];
    for (int i=0;i!=5;i++)
    {
        Array[i]=Old.Array[i];
    }
    return *this;
}
a::a(a&&Old)
{
    Array=Old.Array;
    Old.Array=nullptr;
}
a& a::operator=(a&&Old)
{
    Array=Old.Array;
    Old.Array=nullptr;
    return *this;
}
a::~a()
{
    delete[] Array;
}

int main()
{
    a A(NoConstruct),B(NoConstruct),C;
    A=C;
    B=C;
}

в настоящее время A, B и C имеют разные значения указателя. Я бы хотел, чтобы у A был новый указатель, B имел старый указатель C, а C - нулевой указатель.

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

Ответ 1

Вызывается конструктор перемещения:

  • когда инициализатор объекта std::move(something)
  • когда инициализатор объекта std::forward<T>(something) и T не является ссылочным типом lvalue (полезно в программировании шаблонов для "идеальной пересылки" )
  • когда инициализатор объекта является временным, и компилятор не полностью исключает копирование/перемещение.
  • при возврате объекта local-объекта функции по значению, и компилятор не исключает копирование/перемещение целиком
  • при бросании объекта-объекта локального класса и компилятора не исключается копирование/перемещение целиком

Это не полный список. Обратите внимание, что "инициализатор объекта" может быть аргументом функции, если параметр имеет тип класса (не ссылочный).

a RetByValue() {
    a obj;
    return obj; // Might call move ctor, or no ctor.
}

void TakeByValue(a);

int main() {
    a a1;
    a a2 = a1; // copy ctor
    a a3 = std::move(a1); // move ctor

    TakeByValue(std::move(a2)); // Might call move ctor, or no ctor.

    a a4 = RetByValue(); // Might call move ctor, or no ctor.

    a1 = RetByValue(); // Calls move assignment, a::operator=(a&&)
}

Ответ 2

Прежде всего, ваш конструктор копирования сломан. Оба скопированные и скопированные объекты будут указывать на один и тот же Array и будут пытаться delete[], когда они выйдут из области видимости, что приведет к поведению undefined. Чтобы исправить это, сделайте копию массива.

a::a(const a& Old): Array(new int[5])
{
  for( size_t i = 0; i < 5; ++i ) {
    Array[i] = Old.Array[i];
  }
}

Теперь перемещение назначения не выполняется так, как вы хотите, потому что оба оператора присваивания присваивают значения lvalues ​​вместо использования rvalues. Для перемещений, которые необходимо выполнить, вы должны перейти от rvalue, или это должен быть контекст, где lvalue можно считать rvalue (например, оператор возврата функции).

Чтобы получить желаемый эффект, используйте std::move, чтобы создать ссылку на rvalue.

A=C;              // A will now contain a copy of C
B=std::move(C);   // Calls the move assignment operator