Как написать конструктор перемещения для обработки неинициализированного перемещения? - PullRequest
2 голосов
/ 19 мая 2019

У меня есть класс в моем коде C ++, который имеет свой собственный конструктор перемещения.Здесь показана упрощенная версия:

class myClass {
    //In this example, myClass must manually manage allocating
    //and freeing a memory buffer.
    char *mem;

    //...
    //Regular constructor, copy constructor, etc
    //...

    myClass(myClass &&other) {
        //Swap our memory pointer with other's memory pointer
        char *tmp = other.mem;
        other.mem = mem;
        mem = tmp;
    } 

    //...
    //Destructor, other member functions, etc.
    //...
}

В обычных ситуациях это работает нормально.Однако недавно мне нужно было создать вектор из этих объектов:

vector<myClass> v;
v.reserve(10); //Make space, but do not construct
v.push_back(myClass()); //Problem!

После того, как я получил segfault и перешагнул через gdb, я в конце концов обнаружил то, что должно было быть очевидным: если вы пытаетесь построить объект изСсылка на значение, это может привести к использованию конструктора перемещения в неинициализированной памяти .

Как вы должны написать конструктор перемещения, если возможно, что вы меняете мусор в класс other?Есть ли способ обнаружить это?

Ответы [ 2 ]

6 голосов
/ 19 мая 2019

Как вы должны написать конструктор перемещения, если возможно, что вы меняете мусор в другом классе? Есть ли способ обнаружить это?

Объект, который не инициализирован, содержит неопределенное значение, пока ему не назначено другое значение [basic.indet] / 1 . В принципе, вам нельзя делать что-либо с объектом, имеющим неопределенное значение, за исключением присвоения ему правильного значения [basic.indet] / 2 . Поскольку вам даже не разрешено смотреть на значение, которое содержит объект, если только оно не было инициализировано или ему присвоено значение, не может быть способа обнаружить, был ли объект инициализирован, просто взглянув на сам объект (потому что вы ' не разрешено даже смотреть). Таким образом, строго говоря, вы на самом деле не просто «переставляете значения мусора в другой класс», вы вызываете неопределенное поведение. Обмен мусора - это то, как обычно проявляется это неопределенное поведение.

Решение проблемы простое: убедитесь, что ваш указатель всегда инициализирован действительным значением, например, nullptr:

class myClass {
    //In this example, myClass must manually manage allocating
    //and freeing a memory buffer.
    char *mem = nullptr;

    //...
    //Regular constructor, copy constructor, etc
    //...

    myClass(myClass &&other) {
        //Swap our memory pointer with other's memory pointer
        char *tmp = other.mem;
        other.mem = mem;
        mem = tmp;
    } 

    //...
    //Destructor, other member functions, etc.
    //...
}

Вместо того, чтобы реализовывать конструктор перемещения самостоятельно, рассмотрите, например, просто использование члена типа std::unique_ptr и простую опору на неявно определенный конструктор перемещения. Например:

class myClass
{
    std::unique_ptr<char[]> mem;

    // regular constructor, copy constructor, etc.

    myClass(myClass&&) = default;

    // other member functions, etc.
};
2 голосов
/ 19 мая 2019

Не меняйте указатели в конструкторе. Это не то, как вы пишете конструкторы перемещения. Обмен предназначен для перемещения-назначения, когда оба объекта живы.

Конструкторы существуют для инициализации объекта. Таким образом, память, с которой они начинают, всегда находится в «неинициализированном» состоянии. Поэтому, если вы не инициализируете элемент (или у него не будет конструктор по умолчанию, который его инициализирует для вас), значение элемента будет неинициализировано.

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

myClass(myClass &&other) : mem(other.mem) {
    other.mem = nullptr;
}

Или, с C ++ 14 (и C ++ 20 с constexpr версией), вы можете поменять значение :

myClass(myClass &&other)
  : mem(std::exchange(other.mem, nullptr))
{}
...