简体   繁体   中英

Casting static_cast on dynamically-created element of smartpointers vector

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:

  1. You need to release the person (so it does not delete it).
  2. You need to use 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM