unique_ptr и полиморфизм

У меня есть код, который в настоящее время использует raw указатели, и я хочу перейти на интеллектуальные указатели. Это помогает очистить код различными способами. Во всяком случае, у меня есть заводские методы, которые возвращают объекты, и ответственность за них отвечает за их управление. Владение не разделяется, и поэтому я считаю, что unique_ptr будет подходящим. Объекты, которые я возвращаю, как правило, выводятся из одного базового класса Object.

Например,

class Object { ... };
class Number : public Object { ... };
class String : public Object { ... };

std::unique_ptr<Number> State::NewNumber(double value)
    {
        return std::unique_ptr<Number>(new Number(this, value));
    }

std::unique_ptr<String> State::NewString(const char* value)
    {
        return std::unique_ptr<String>(new String(this, value));
    }

Возвращаемые объекты довольно часто должны передаваться другой функции, которая работает с объектами типа Object (базовый класс). Этот код не содержит никаких умных указателей.

void Push(const Object* object) { ... } // push simply pushes the value contained by object onto a stack, which makes a copy of the value
Number* number = NewNumber(5);
Push(number);

При преобразовании этого кода для использования unique_ptrs я столкнулся с проблемами с полиморфизмом. Первоначально я решил просто изменить определение Push для использования unique_ptrs тоже, но это создает ошибки компиляции при попытке использовать производные типы. Я мог бы выделить объекты в качестве базового типа, например

std::unique_ptr<Object> number = NewNumber(5);

и передать их Push - что, конечно же, работает. Однако мне часто приходится вызывать методы производного типа. В конце концов я решил сделать Push на указатель на объект, сохраненный unique_ptr.

void Push(const Object* object) { ... }
std::unique_ptr<Object> number = NewNumber(5);
Push(number.get());

Теперь о причине публикации. Я хочу знать, является ли это обычным способом решения проблемы, которая у меня была? Лучше ли использовать Push на unique_ptr против самого объекта? Если да, то как решить проблемы полиморфизма? Я бы предположил, что просто бросать птры не получится. Общеизвестно, что нужно получить основной указатель от умного указателя?

Спасибо, извините, если вопрос непонятен (просто дайте мне знать).

edit: Я думаю, что моя функция Push была немного неоднозначной. Он создает копию базового значения и фактически не изменяет или не сохраняет входной объект.

Ответ 1

Первоначально я решил просто изменить определение Push для использования unique_ptrs тоже, но это создает ошибки компиляции при попытке использовать производные типы.

Вероятно, вы не правильно относились к уникальности.

void push(std::unique_ptr<int>);
int main() {
    std::unique_ptr<int> i;
    push(i); // Illegal: tries to copy i.
}

Если это скомпилировано, это тривиально разрушит инвариант unique_ptr, что только один unique_ptr владеет объектом, потому что и i и локальный аргумент в push будут иметь этот int, поэтому он является незаконным. unique_ptr - только перемещение, оно не копируется. Он не имеет ничего общего с преобразованием базы данных в базовое преобразование, которое unique_ptr обрабатывает полностью правильно.

Если push принадлежит объекту, используйте std::move чтобы переместить его туда. Если это не так, используйте необработанный указатель или ссылку, потому что это то, что вы используете для псевдонинга без прав собственности.

Ответ 2

Ну, если ваши функции работают с самим объектом (указывается) и не нуждаются в его адресе, не берут на себя никакой собственности и, как я полагаю, всегда нужен действительный объект (сбой при передаче nullptr), почему они берут указатели на всех?

Делайте это правильно и заставляйте их брать ссылки:

void Push(const Object& object) { ... }

Тогда вызывающий код выглядит точно так же для сырых и умных указателей:

auto number = NewNumber(5);
Push(*number);

EDIT: Но, конечно, независимо от того, используете ли ссылки или указатели, не делайте Push принимайте std::unique_ptr если он не принимает права собственности на переданный объект (что приведет к краже собственности из переданного указателя). Или, вообще говоря, не используйте указатели владения, когда объект, на который указывает объект, не принадлежит, std::shared_ptr не является чем-то другим в этом отношении и хуже, чем параметр std::unique_ptr для Push если есть никакая собственность не должна быть предпринята Push.

Ответ 3

Если Push не принимает owenrship, скорее всего, вместо указателя следует использовать ссылку. И наиболее вероятно const один. Таким образом, вы будете иметь

Push(*number);

Теперь это очевидно только в том случае, если Push не будет содержать указатель где-нибудь за ним. Если я подозреваю, что вы должны сначала попытаться переосмыслить право собственности.

Ответ 4

Здесь приведен пример полиморфизма с использованием уникального указателя:

vector<unique_ptr<ICreature>> creatures;

creatures.emplace_back(new Human);
creatures.emplace_back(new Fish);

unique_ptr<vector<string>> pLog(new vector<string>());

for each (auto& creature in creatures)
{
    auto state = creature->Move(*pLog);
}