Вернуть объект RAII из функции - PullRequest
0 голосов
/ 24 февраля 2020

В C ++ 14 я пытаюсь определить функцию «фабрики RAII», что-то вроде этого:

// Here, "ScopedReseource" is a plain RAII class, managing some resource.

ScopedResource factory(/* args */) {
    return ScopedReseource(/* args */);
}

Использование клиента будет:

{
    auto scoped = factory(/* args */));
    // use scoped...
}

Как я поймите это, исключение копирования не гарантируется языком в C ++ 14, так что это ненадежно. В частности, это может привести к тому, что деструктор ScopedResource будет вызываться в конце функции factory() (и новая копия будет сделана на месте вызова). Я не хочу этого.

Традиционный клиентский код работает нормально, конечно:

{
    ScopedResource scoped(/* args */);
    // use scoped...
}

Теперь я попытался удалить конструктор копирования ScopedResource (=delete) и определение конструктора перемещения. Код компилируется, и он только один раз создает / уничтожает, что я и хочу - но переносимо ли это?

Итак, вопросы:

  • Прав ли я, возвращая RAII объект (где деструктор выполняет специальную работу, которую я хочу сделать ровно один раз) невозможен до C ++ 17?
    • Является ли мой подход удаления конструктора копирования и определения конструктора перемещения действительным / переносимым?
  • Я прав, что в C ++ 17 вы можете переносимо сделать это с приведенным выше наивным кодом?

Или здесь есть какие-то другие нюансы, которые мне не хватает?

Ответы [ 2 ]

3 голосов
/ 25 февраля 2020

Насколько я понимаю, копирование не гарантируется языком в C ++ 14, поэтому это ненадежно. В частности, это может привести к тому, что деструктор ScopedResource будет вызываться в конце функции factory () (и новая копия будет сделана на месте вызова). Я не хочу этого.

Почему бы и нет?

Если ваш тип является перемещаемым, то это означает, что перемещенный объект будет содержать «нулевой» ресурс. То есть в настоящее время он не будет связан с ресурсом. Следовательно, его деструктор ничего не будет делать, а управляемый ресурс все еще будет существовать.

Так что в C ++ 14 нет проблем с возвратом такого объекта по значению. Да, деструктор может быть вызван, но так как он ничего не делает, кого это волнует?

Прав ли я, возвращая объект RAII (где деструктор выполняет специальную работу, которую я хочу сделать ровно один раз? ) не выполнимо до C ++ 17?

Нет, вы не правы. До тех пор, пока вы кодируете свой объект, чтобы иметь «нулевое» состояние, которое означает отсутствие ресурса (что является стандартным для типов только для перемещения), «специальная работа» будет выполняться только одним объектом. Может быть вызвано несколько деструкторов, но только один такой вызов будет выполнять важную часть освобождения ресурса.

Речь идет о правильной реализации типа «только для перемещения», а не об исключении копирования.

1 голос
/ 24 февраля 2020

Прав ли я, что возвращение объекта RAII (где деструктор выполняет специальную работу) невозможно до C ++ 17?

В общем, нет. Тип RAII должен быть создан для обработки копирования / перемещения, чтобы его можно было возвращать по значению.

В вашем конкретном случае c вы правы, что вам требуется гарантированное разрешение на копирование в C ++ 17, поскольку ваш RAII Тип не делает этого. (Что означает, что это не тип RAII)

Является ли мой подход удаления конструктора копирования и определения конструктора перемещения действительным / переносимым?

Это правильный способ остановите копирование объекта, но снова до C ++ 17 объект мог быть удален из функции, и деструктор запустил заглушку, оставленную в функции. Если вашей целью является то, что деструктор не вызывается до тех пор, пока объект не будет уничтожен на сайте вызова, тогда это не решение для вас.

Прав ли я, что в C ++ 17 вы можете переносить это с наивным кодом выше?

Да. В C ++ 17 и выше

ScopedResource factory(/* args */) {
    return ScopedReseource(/* args */);
}

auto scoped = factory(/* args */));

сводится к

auto scoped = ScopedReseource(/* args */);

Если вы не можете гарантировать, что у вас будет C ++ 17, тогда вы можете используйте std::unique_ptr для инкапсуляции вашего объекта. Это гарантирует, что даже если RVO / NRVO не будет применен, ваш объект не будет уничтожен. Это дало бы вам

std::unique_ptr<ScopedResource> factory(/* args */) {
    return make_unique<ScopedReseource>(/* args */);
}
...