У меня есть код, который в настоящее время использует сырые указатели, и я хочу перейти на умные указатели. Это помогает очистить код различными способами. Во всяком случае, у меня есть фабричные методы, которые возвращают объекты и ответственность вызывающего их за управление ими. Владение не разделяется, и поэтому я считаю, 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
против самого объекта? Если так, то как можно решить проблемы полиморфизма? Я предполагаю, что простое приведение ptrs не сработает. Часто ли требуется получить базовый указатель от умного указателя?
Спасибо, извините, если вопрос не ясен (просто дайте мне знать).
редактировать: я думаю, что моя функция Push была немного неоднозначной. Он создает копию базового значения и фактически не изменяет и не сохраняет входной объект.
Изначально я решил просто изменить определение 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
переместить его туда. Если это не так, используйте необработанный указатель или ссылку, потому что это то, что вы используете для псевдонима, не являющегося владельцем.
Что ж, если ваши функции работают с самим (указанным) объектом и не нуждаются в его адресе, не вступают в права собственности и, как я полагаю, всегда нуждаемся в действительном объекте (ошибка при передаче nullptr
), почему они вообще используют указатели?
Сделайте это правильно и заставьте их брать ссылки:
void Push(const Object& object) { ... }
Тогда вызывающий код выглядит одинаково для необработанных и умных указателей:
auto number = NewNumber(5);
Push(*number);
РЕДАКТИРОВАТЬ: Но, конечно, независимо от того, используете ли ссылки или указатели, не делайте Push
взять std::unique_ptr
если он не вступает во владение переданным объектом (что может заставить его украсть владение из переданного указателя). Или вообще не используйте указатели владения, когда указатель на объект не должен быть владельцем, std::shared_ptr
ничем не отличается в этом отношении и является столь же худшим выбором, как std::unique_ptr
за Push
параметр, если нет права собственности Push
,
Если Push
не принимает owenrship, скорее всего, вместо указателя следует использовать ссылку. И, скорее всего, const
один. Итак, вы будете иметь
Push(*number);
Теперь это очевидно только в том случае, если Push не будет удерживать указатель где-либо после его возврата. Если это так, я подозреваю, что вы должны сначала переосмыслить право собственности.
Вот пример полиморфизма с использованием уникального указателя:
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);
}