Подтвердить что ты не робот

Каково поведение конструктора перемещения сгенерированного компилятором?

Имеет ли std::is_move_constructible<T>::value == true, что T имеет полезный конструктор перемещения? Если да, то каково его поведение по умолчанию?

Рассмотрим следующий случай:

struct foo {
    int* ptr;
};

int main() {
    {       
        std::cout << std::is_move_constructible<foo>::value << '\n';
        foo f;
        f.ptr = (int*)12;
        foo f2(std::move(f));
        std::cout << f.ptr << ' ' << f2.ptr << '\n';
    }
    return 0;
}

а выход:

1
0000000C 0000000C

Я думал, что f.ptr должен быть nullptr. Итак, в этом случае

  • Строка f2 построена?
  • Если это так, не следует ли признавать значение rvalue?
  • Как узнать, могут ли экземпляры класса корректно перемещаться (недействительны старые)?

(Я использую VS11.)

Update

Поведение конструктора перемещения по умолчанию такое же, как конструктор копирования, правильно ли оно? Если это правда,

  • Мы всегда ожидаем перемещения ctor, чтобы украсть ресурсы перемещенного объекта, в то время как по умолчанию он не ведет себя так, как ожидалось, так что точка, связанная с перемещением по умолчанию?
  • Как узнать, имеет ли класс специальный конструктор перемещения (который может быть гарантированно корректно вести себя)?

Кажется, что foo f2(std::move(f)); вызывает копию ctor, когда я ее объявлял, см.

struct foo {
    int* ptr;
    foo() {}
    foo(const foo& other) {
        std::cout << "copy constructed\n";
    }
};

int main() {
    {       
        std::cout << std::is_move_constructible<foo>::value << '\n';
        foo f;
        foo f2(std::move(f));
    }
    system("pause");
    return 0;
}

Теперь вывод:

1
copy constructed

Если foo имеет конструктор перемещения, то не будет foo f2(std::move(f)) называть его?

Итак, теперь мои вопросы: Как узнать, имеет ли класс перемещение ctor, и имеет ли он его, как я могу его явно вызвать?

То, что я пытаюсь сделать, это...

template<typename T, bool has_move_ctor>
struct MoveAux;

template<typename T>
struct MoveAux<T, true> {
    static void doMove(T* dest, T* src) {
        new(dest) T(std::move(*src)); //move ctor
    }
};

template<typename T>
struct MoveAux<T, false> {
    static void doMove(T* dest, T* src) {
        new(dest) T(*src); //copy ctor
        src->~T();
    }
};

template<typename T>
inline doMove(T* dest, T* src) {
    MoveAux<T,/*a trait*/>::doMove(dest, src);
}

Итак, я думал, что std::is_move_constructible<T>::value может быть передан шаблону, а теперь я вижу, что этот признак заботится только о том, является ли T t(T()) допустимым выражением, он может вызвать T::T(const T&). Предположим, что T - это пользовательский класс, тогда я хочу, чтобы приведенные выше шаблоны действовали так:

  • Если я не объявляю перемещение ctor, я хочу, чтобы этот метод шаблона вызывал MoveAux<T,false>::doMove.
  • Если я объявил один, мне нужно, чтобы он вызывал MoveAux<T,true>::doMove.

Можно ли выполнить эту работу?

4b9b3361

Ответ 1

имеет ли std::is_move_constructible<T>::value == true, что T имеет удобный конструктор перемещения?

Либо конструктор перемещения, либо конструктор копирования. Помните, что операция построения копии удовлетворяет всем требованиям, предъявляемым к конструкции перемещения операции, и еще кое-что.

В стандартных терминах объект MoveConstructible - это объект, для которого выполняется оценка выражения:

T u = rv; 

делает u эквивалентным значению rv до построения; состояние rv после перемещения - из неуказан. Но поскольку это не указано, это означает, что состояние может быть даже идентичным тому, которое было rv до того, как оно было перенесено из: Другими словами, u может быть копией rv.

Фактически, стандарт определяет концепцию CopyConstructible как уточнение понятия MoveConstructible (так что все, что есть CopyConstructible, также MoveConstructible, но не наоборот).

если да, каково его поведение по умолчанию?

Поведение неявно сгенерированного конструктора перемещения состоит в том, чтобы выполнить поэтапное перемещение элементов данных того типа, для которого он сгенерирован.

Per Parahgraph 12.8/15 стандарта С++ 11:

Неявно заданный конструктор копирования/перемещения для неединичного класса X выполняет постинговое копирование/перемещение его оснований и членов. [Примечание: скопированные или равные инициализаторы нестатических членов данных игнорируются. Видеть также пример в 12.6.2. -end note]

При этом:

1 - это f2 перемещение построено?

Да.

2 - если да, то не должно ли значение rvalue быть недействительным?

Перемещение указателя совпадает с копированием. Таким образом, никакой аннулирования не происходит, и не должно продолжаться. Если вы хотите, чтобы конструктор перемещения оставил перемещенный объект в определенном состоянии (т.е. задает член данных указателя на nullptr), вы должны написать свой собственный - или делегировать эту ответственность на некоторый класс интеллектуальных указателей, например std::unique_ptr.

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

Другими словами, необходимо учитывать инвариант класса - и его можно было бы вызывать на перенесенных объектах, которые не имеют каких-либо предварительных условий в его состоянии (обычно, уничтожении и присваивании).

Ответ 2

делает std:: is_move_constructible:: value == true означает, что T имеет полезный конструктор перемещения?

Нет. В нем указано, что вы можете принять выражение rvalue типа объекта и построить из него объект. Независимо от того, использует ли это конструктор перемещения или конструктор копирования, это не имеет отношения к этому признаку.

- движение f2 построено?

Да.

если это так, не должно ли rvalue быть недействительным?

Нет. Это не то, как работает движение.

как я могу узнать, могут ли экземпляры класса корректно перемещаться (недействительны старые)?

Это не какое-либо определение "правильного перемещения", которое существует. Если вы хотите "аннулировать старый", вам придется сделать это самостоятельно.

Конструкция перемещения вообще ничего не гарантирует о состоянии старого объекта. Он будет в состоянии, но undefined. Такое состояние очень может быть "таким же, каким оно было раньше". Перемещение конструкции для указателя совпадает с копированием указателя.

Если вы хотите "аннулировать" после перемещения, вам нужно написать свой собственный конструктор перемещения, который явно делает это.

(Я использую VS11)

Тогда у вас вообще нет конструкторов перемещения компилятора. Не то чтобы это имело бы значение, так как конструкторы перемещения и копирования для указателей делают то же самое.

Ответ 3

Обратите внимание, что Visual Studio 2012/VС++ 11 не поддерживает создаваемые компилятором конструкторы перемещения; на самом деле, рассмотрите эту цитату из "С++ 11 Features in Visual С++ 11" в блоге (основное внимание):

Ссылки Rvalue v3.0 добавляют новые правила для автоматического создания перемещения конструкторов и операторов присваивания при определенных условиях. Этот не будет реализован в VC11, который будет продолжать следовать Поведение VC10 никогда автоматически не генерирует ход конструкторы/переместить операторы присваивания.

С помощью необработанных указателей вы должны сами определить конструкторы перемещения, вручную очистив старый указатель "перемещенный из":

class Foo 
{
public:

    // Move constructor
    Foo(Foo&& other)
        : m_ptr(other.m_ptr) // copy pointer value
    {
        // Clear out old "moved-from" pointer, to avoid dangling references
        other.m_ptr = nullptr;
    }

private:
    int* m_ptr;
};

Вместо этого, если вы используете умный указатель, например std::unique_ptr, переместите конструктор правильно, и вы можете просто вызвать std::move:

class Foo 
{
public:

    // Move constructor
    Foo(Foo&& other)
        : m_ptr(std::move(other.m_ptr)) // move from other, 
                                        // old pointer automatically cleared
    {
    }

private:
    std::unique_ptr<int> m_ptr;
};

С автоматически создаваемыми конструкторами перемещения вам не нужно явно указывать конструктор пользовательского перемещения, если членский подход для вас подходит.

Ответ 4

поведение defaulr конструктора перемещения такое же, как и копия конструктор, это правильно? если оно истинно

Нет. Это не правильно. Это верно только для примитивов. Он похож на конструктор копирования.

Созданный по умолчанию конструктор копирования вызывает конструктор copy всех его членов в объявленном порядке

Но созданный по умолчанию конструктор перемещения вызывает конструктор move всех его членов в объявленном порядке

Теперь следующий вопрос: что такое конструктор copy/move примитивов int float pointer do?

Ans: они просто копируют значения (как копировать, так и перемещать конструктор)

Ответ 5

n3376 12.8/15

Неявно определенный конструктор move/ move для неединичного класса X выполняет элемент copy/ move его оснований и членов.

Каждая базовая или нестатическая информация член копируется/перемещается в соответствии с его типом:

- если элемент является массивом, каждый элемент напрямую инициализируется соответствующим подобъектом x;

- если элемент m имеет ссылочный тип rvalue T & &, он напрямую инициализируется static_cast (x.m);

- в противном случае база или элемент инициализируется с прямой базой или элементом x.

Ответ 6

Если foo имеет конструктор перемещения, то не будет foo f2 (std:: move (f)) вызывает его? Вы не получаете конструктор перемещения по умолчанию при поставке своего конструктора копий. Добавьте следующую строку, чтобы получить ее (и обратите внимание на изменение). foo (foo && ifm) = default;