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

Может `* this` быть` move() `d?

Я хотел бы определить класс для сортировки данных; когда маршаллинг закончен, я хотел бы move отобразить данные изнутри, что, вероятно, приведет к аннулированию объекта маршаллинга.

Я считаю, что это возможно с помощью функции static extractData ниже:

class Marshaller
{
  public:
    static DataType extractData(Marshaller&& marshaller)
    {
      return std::move(marshaller.data);
    }
  private:
    DataType data;
}

Это немного неудобно звонить, хотя:

Marshaller marshaller;
// ... do some marshalling...
DataType marshalled_data{Marshaller::extractData(std::move(marshaller))};

Могу ли я обернуть его с помощью функции-члена?

DataType Marshaller::toDataType()
{
  return Marshaller::extractData(std::move(*this));
}

Это, конечно, можно было бы назвать с помощью:

DataType marshalled_data{marshaller.toDataType()};

... который, для меня, выглядит намного приятнее. Но эта вещь std::move(*this) выглядит ужасно подозрительной. В контексте вызова toDataType(), marshaller нельзя использовать снова, но я не думаю, что компилятор может знать, что: тело функции может находиться вне блока компиляции вызывающего, поэтому нет ничего что marshaller применил к нему move().

Это поведение undefined? Это прекрасно? Или где-то посередине? Есть ли лучший способ достичь той же цели, желательно без использования макроса или для явного вызова вызывающего? move marshaller?

РЕДАКТИРОВАТЬ: Как с g++, так и с Clang++, я обнаружил, что не только могу скомпилировать вышеупомянутый вариант использования, но я мог бы продолжать вносить изменения в базовые данные через маршаллер, извлеките измененные данные с помощью функции toDataType. Я также обнаружил, что уже извлеченные данные в marshalled_data продолжали изменяться на marshaller, что указывает на то, что marshalled_data разделяется между marshaller и вызывающим контекстом, поэтому я подозреваю, что есть либо память -leak или undefined (от двойного удаления).

РЕДАКТИРОВАТЬ 2: Если я поставлю оператор печати в деструктор DataType, он появится дважды, когда вызывающий абонент покинет область видимости. Если я включаю элемент данных в DataType, у которого есть массив в нем, с соответствующими new[] и delete[], я получаю a glibc "двойное свободное или повреждение". Поэтому я не уверен, как это может быть безопасно, хотя в нескольких ответах сказано, что это технически разрешено. Полный ответ должен объяснить, что необходимо для правильного использования этой методики с нетривиальным классом DataType.

РЕДАКТИРОВАТЬ 3: Это достаточно кроличьей дыры/червяков, которые я открыл еще один вопрос обратитесь к моим оставшимся проблемам.

4b9b3361

Ответ 1

В вызове move(*this) нет ничего по своей сути небезопасно. move по существу является лишь намеком на вызываемую функцию, что он может украсть внутренности объекта. В системе типов это обещание выражается через ссылки &&.

Это никак не связано с уничтожением. move не выполняет никакого типа разрушения - как уже упоминалось, он просто позволяет нам вызывать функции, принимающие параметры &&. Функция, получающая перемещенный объект (extractData в этом случае), также не уничтожает. Фактически, он должен оставить объект в "действительном, но неуказанном состоянии". По сути, это означает, что должно быть возможно уничтожить объект обычным способом (через delete или путем выхода из области видимости в зависимости от того, как он был создан).

Итак, если ваш extractData делает то, что должен, и оставляет объект в состоянии, которое позволяет ему быть уничтожено позже - нет ничего undefined или опасно для компилятора. Конечно, может возникнуть проблема с тем, что пользователи путают код, поскольку не совсем очевидно, что объект перемещается из (и, скорее всего, он не будет содержать никаких данных позже). Возможно, это можно сделать немного яснее, изменив имя функции. Или (как предложил другой ответ) на && -qualifying весь метод.

Ответ 2

В соответствии со стандартом объект move-from будет по-прежнему действителен, хотя его состояние не гарантируется, поэтому кажется, что переход от *this был бы абсолютно корректным. Является ли это запутанным для пользователей вашего кода, это еще один вопрос.

Все, что говорило, похоже, что ваше реальное намерение - связать уничтожение маршаллара с извлечением данных. Считаете ли вы, что все это происходит в одном выражении и позволяете временно заботиться о вас?

class Marshaller
{
  public:
    Marshaller& operator()(input_data data) { marshall(data); return *this; }
    DataType operator()() { return std::move(data_); }
  private:
    DataType data_;
}

DataType my_result = Marshaller()(data1)(data2)(data3)();

Ответ 3

Я бы не переместился с *this, но если вы это сделаете, по крайней мере, вы должны добавить в функцию ref-qualifier rvalue:

DataType Marshaller::toDataType() &&
{
    return Marshaller::extractData(std::move(*this));
}

Таким образом, пользователь должен будет вызвать его следующим образом:

// explicit move, so the user is aware that the marshaller is no longer usable
Marshaller marshaller;
DataType marshalled_data{std::move(marshaller).toDataType()};

// or it can be called for a temporary marshaller returned from some function
Marshaller getMarshaller() {...}
DataType marshalled_data{getMarshaller().toDataType()};

Ответ 4

Я думаю, что вы не должны перемещаться из *this, а из поля data. Поскольку это явно оставит объект Marshaller в допустимом, но непригодном для использования состоянии, функция-член, которая делает это, сама должна иметь квалификатор ссылки rvalue в своем неявном аргументе *this.

class Marshaller
{
public:
  ...
  DataType Marshaller::unwrap() &&   { return std::move(data); }

  ...
private:
  DataType data;
};

Вызовите его, если m является переменной Marshaller, как std::move(m).unwrap(). Для этого не требуется никакого статического члена.

Ответ 5

Вы пишете, что хотите одновременно уничтожить маршаллера и удалить данные из него. Я действительно не стал бы беспокоиться о попытке сделать это одновременно, просто перенесите данные сначала, а затем уничтожьте объект Marshaller. Есть несколько способов избавиться от Маршаллера, не задумываясь об этом, возможно, умный указатель имеет смысл для вас?

Один из вариантов для рефакторинга заключается в предоставлении DataType конструктора, который принимает маршаллер и перемещает данные (ключевое слово "friend" позволит вам сделать это, так как DataType сможет достичь этих частных "данных", переменная).

    //add this line to the Marshaller
    friend class DataType;

struct DataType
{
    DataType(Marshaller& marshaller) {
             buffer = marshaller.data.buffer;
        }

    private: 
        Type_of_buffer buffer;//buffer still needs to know how to have data moved into it
}

Вы также можете дать ему оператор присваивания, который делает то же самое (я думаю, что это будет просто работать:

DataType& operator=(Marshaller&& marshaller) {
     this.buffer = std::move(marshaller.data.buffer);
     return *this;
}

)

Я бы избегал использовать move * this, просто потому, что он собирается отбрасывать людей, даже если это правильно. Также кажется, что буферные контейнеры на основе стека могут вызвать у вас проблемы.

Кажется, вы беспокоитесь о том, что Маршаллер снова вызывается за пределами модуля компиляции. Если у вас есть интенсивный параллельный код, и вы играете быстро и свободно с маршаллером, или вы копируете указатели на свой маршаллер волей-неволей, я думаю, ваши заботы оправданы. В противном случае рассмотрите, как перемещается Marshaller, и убедитесь, что вы структурировали свой код для хорошего времени жизни объекта (используйте ссылки на объекты, когда можете). Вы также можете просто добавить флаг участника в marshaller, который говорит, были ли перемещены данные и выбрасывается ошибка, если кто-то пытается получить к ней доступ после ее останова (если вы параллельны, обязательно заблокируйте). Я бы сделал это только в крайнем случае или быстро исправить, так как это не кажется правильным, и ваши со-разработчики будут задаваться вопросом, что случилось.

У меня есть несколько нит, чтобы выбрать, если у вас есть момент:

  • В методе extractData отсутствует ключевое слово static
  • вы смешиваете скобки и круглые скобки в строке объявления DataType