0

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
Dorin Baba
  • 1,578
  • 1
  • 11
  • 23
Adato
  • 423
  • 4
  • 20
  • See:: https://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-const-cast-and-reinterpret-cast-be-used – zkoza Dec 28 '20 at 22:08

3 Answers3

4

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_ptrs 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 virtualness)

    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_ptrs 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.

HTNW
  • 27,182
  • 1
  • 32
  • 60
  • i realized that creating ptr was crashing the vector. Just using std::move(v1[1]) did the job. – Adato Dec 29 '20 at 15:53
0

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).
Martin York
  • 257,169
  • 86
  • 333
  • 562
0

You cannot cast std::unique_ptr, but you can do it for its managed pointer:

auto* walkers = static_cast<Walker*>(ptr.get());
Jarod42
  • 203,559
  • 14
  • 181
  • 302