I'm learning C++ and I'm facing a problem. I have created a vector of smartpointers containing dynamically-created objects of a derived class but I am unable to cast a static_cast on one of these objects. I have tried several approaches but it didn't work.
this is my code:
class Person {};
class Walker : public Person {};
std::vector<std::unique_ptr<Person>>v1;
for (int i = 0; i < 4; i++) { v1.push_back(std::unique_ptr<Person>(new Walker) ); }
//trying to cast static_cast on element on position 1:
std::unique_ptr<Person>ptr = move(v1[1]);
auto walkers = static_cast<Walker*>(ptr);//doesnt work - no suitable conversion function
You need to unwrap the raw pointer out of the unique_ptr
first:
auto walkers = static_cast<Walker*>(ptr.get());
Note that the ownership of the pointer still belongs to ptr
, which is a std::unique_ptr<Person>
still. You can transfer the ownership like this:
std::unique_ptr<Walker> walkers(static_cast<Walker*>(ptr.release()));
If you want, you can write a function like this
template<typename Target, typename Source>
auto static_cast_unique(std::unique_ptr<Source> ptr) {
return std::unique_ptr<Target>(static_cast<Target*>(ptr.release()));
}
So you can write
auto walkers = static_cast_unique<Walker>(std::move(v1[1]));
Now, some issues with your code:
Person
needs a virtual destructor. You have Walker
objects owned by std::unique_ptr<Person>
s. That's OK in itself, but when those unique_ptr
s try to destroy their objects, they will call ~Person
, not ~Walker
(since that is what their type says to do), and that will be UB. Give Person
a virtual ~Person()
(and Walker
will inherit that virtual
ness)
class Person { public: virtual ~Person() = default; }; class Walker: public Person {};
std::unique_ptr(new...)
is better written with std::make_unique
for (int i = 0; i < 4; i++) v1.push_back(std::make_unique<Walker>());
std::unique_ptr
s will automatically upcast.
In this case, it seems OK to use static_cast
, because you know that the Person*
you have actually points to a Walker
. However, you will want to use dynamic_cast
in the general case, so you can detect when the object isn't of the type you expect. I would use a function like this
template<typename Target, typename Source> std::unique_ptr<Target> dynamic_cast_unique(std::unique_ptr<Source> &&ptr) { if(auto ret = dynamic_cast<Target*>(ptr.get())) { ptr.release(); return std::unique_ptr<Target>(ret); } else return nullptr; }
Which is used like
auto walkers = dynamic_cast_unique<Walker>(std::move(v1[1]));
If it succeeds, walkers
will own the object previously owned by v1[1]
and v1[1]
will be empty. Otherwise, walkers
will be empty and v1[1]
will be unchanged.
This is probably not what you want to do!
The Person
class should have a virtual interface that Walker
implements so you don't need to convert a Person pointer to a Walker pointer.
That being said:
To solve your current problem:
std::unique_ptr<Walker> getWalkerFromPerson(std::unique_ptr<Person> person)
{
// Note there is still an issue here.
// If this is not a Walker then it will leak
// the person as dynamic_cast will give you a nullptr
return std::unique_ptr<Walker>(dynamic_cast<Walker*>(person.release()));
}
Then call like this:
auto walkers = getWalkerFromPerson(std::move(v1[0]));
Some pointers:
person
(so it does not delete it).dynamic_cast
(to cast from parent towards an unknown (run-time known) child type). You cannot cast std::unique_ptr
, but you can do it for its managed pointer:
auto* walkers = static_cast<Walker*>(ptr.get());
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.