C++ - Cast/Change-type of an unique_ptr
Asked Answered
P

5

6

Edit: Thanks, amazing help, as always :)

I can't find the solution to this, I have an unique_ptr to a base class, that has the data of a derived class, and I want to set it's type to the derived class so I can access the derived members. See this code:

#include <memory>
#include <iostream>

class Base
{
public:
    int v1 = 0xAAAAAAAA;
};

class Derived : public Base
{
public:
    int v2 = 0xBBBBBBBB;
};

int main()
{

    std::unique_ptr<Base> b1(new Derived); //How to access b1->v2 ?
    std::unique_ptr<Base> b2(new Base);


    std::getchar(); 
    return 0;
}

The type of b1 is Base but it's data contains the data of Derived. See:

Screenshot of the problem. We can see data bytes corresponding to Derived

Is it hard to get ? I thought about manipulating memory bytes, like saying [b1+4] (pseudo thoughts) and accessing it, but I think about complicated objects, because I'm doing an entity system for a game, I can't do this :(

Thanks !

Phineas answered 11/11, 2015 at 11:24 Comment(3)
Uh.... by casting to the derived reference/pointer type?Morty
I've tried (Derived*)b1->v2 but it doesn't seems to work, if this is what you meanPhineas
@vishal -> Well, I could do this, but actually I have a bunch of unique_ptr in a vector<Base> so I have entities of different types (Derived1, Derived2 etc) stored in 1 vector<Base> I just took that example without the vector part to make it clearerPhineas
A
5

Your options are:

1) Cast C-like pointer (recommended)

std::unique_ptr<Base> b2(new Base);
Derived * p = static_cast<Derived *>(b2.get());

2) Implement your own static_unique_ptr_cast where you delete b1 and create a new std::unique_ptr:

template<typename Derived, typename Base, typename Del>
std::unique_ptr<Derived, Del> 
static_unique_ptr_cast(std::unique_ptr<Base, Del> && p)
{
    auto d = static_cast<Derived *>(p.release());
    return std::unique_ptr<Derived, Del>(d, std::move(p.get_deleter()));
}

The function takes rvalue reference to ensure you're not stealing the resource from lvalue. Usage:

std::unique_ptr<Base> b1(new Derived);
std::unique_ptr<Derived> p = static_unique_ptr_cast<Derived>(std::move(b1));

Note: If you think you need to use 2) I would consider your design being flawed. The cast is not in the STL for some reason.

Edit: The static_unique_ptr_cast now keeps the deleter.

Ambitious answered 11/11, 2015 at 11:34 Comment(1)
Dangerous answer. static_cast is no good here, it should be dynamic_cast. static_cast doesn't type-check the argument, godbolt. I assume the example code in 1) is undefined behavior as Base is static_casted to Derived, which it isn't, so the output pointer is bogus. As others have pointed out though, if you need to downcast objects the base design is flawed.Henchman
S
1

As Sneftel suggested, you can simply cast the pointer contained within the unique_ptr in order to access the other methods.

static_cast<Derived*>(b1.get())->v2

Keep in mind that this seems strange though. Generally if you have access to a pointer of a given type, you should not be casting it in the hope it has the type you have in mind. Instead, if you need to access methods of a given subclass, you should require that your inputs are pointers of that subclass. Storing base classes in smart pointers is generally done to leverage polymorphism, not much else.

If you are not using polymorphism, consider using a unique_ptr with the derived class in the template, and then downcasting that to the base pointer when just the base class interface is needed.

Another option is to keep the same pointer stored in two places: one as a Base, and one as a Derived - keeping in mind you'll have to use smart pointers only one one of those!

Sienna answered 11/11, 2015 at 11:34 Comment(1)
By the way if I could accept more than one answer I would have checked the tick :( Thanks a bunch !Phineas
L
1

You can use the shared pointer casts if you use shared_ptr

std::static_pointer_cast<Derived>(b1)->v2;

your whole question is explained on the cppreference site: http://en.cppreference.com/w/cpp/memory/shared_ptr/pointer_cast

I would be careful of the other suggestions, the standard did think of the issue and implemented a clean solution.

Longicorn answered 11/11, 2015 at 11:39 Comment(1)
Actually I'm going to read more about it, I might use that in the futurePhineas
B
1

I am not sure that I understand what you are trying to do, but it looks more like a design matter.

If you have a pointer to Base, the actual object might be a Base or a derived class (not necessary Derived). That is, you cannot cast it to Derived without making some dangerous assumptions.

class B{public:virtual ~B(){}};
class D : public B{};
class DD : public B{};

int main()
{
  std::unique_ptr< B > pb( new D );
  D* pd = dynamic_cast< D* >( pb.get() );
  DD* pdd = dynamic_cast< DD* >( pb.get() ); // null
  return 0;
}
Bogbean answered 11/11, 2015 at 11:56 Comment(1)
I know what you mean, I know that's quite dangerous, but I'm trying to make a design pattern with ids to keep track of every object, I'm exploring some c++ stuff, I might come with another idea, beacause, well, you explained the issue, I'm just trying to understand stuff, nothing professional :)Phineas
K
0

I wasn't able to make proposed static_unique_ptr_cast working. Here is a modified version with example:

#include <iostream>
#include <memory>

template<typename Derived, typename Base>
std::unique_ptr<Derived>
static_unique_ptr_cast(std::unique_ptr<Base> && p)
{
    auto d = dynamic_cast<Derived *>(p.release());
    return std::unique_ptr<Derived>(d);
}

template<typename Derived, typename DelDerived, typename Base, typename DelBase>
std::unique_ptr<Derived, DelDerived>
static_unique_ptr_cast_del(std::unique_ptr<Base, DelBase> && p)
{
    auto d = dynamic_cast<Derived *>(p.release());
    return std::unique_ptr<Derived, DelDerived>(d, reinterpret_cast<DelDerived>(std::move(p.get_deleter())));
}

class Base {
public:
    virtual void Do() { std::cout << "Base::Do" << std::endl; }
};

class Derived: public Base {
public:
    void Do() override { std::cout << "Derived::Do" << std::endl; }
};

template<typename T>
void destroy_der(T* p) {
    auto der = dynamic_cast<Derived*>(p);
    std::cout << "destroy_derived" << std::endl;
    delete der;
}

int main(void) {

    {
        std::unique_ptr<Base> b(new Derived);
        std::unique_ptr<Derived> d = static_unique_ptr_cast<Derived>(std::move(b));
        d->Do();
    }
    {
        std::unique_ptr<Base, void(*)(Base*)> b(new Derived, destroy_der<Base>);
        std::unique_ptr<Derived, void(*)(Derived*)> d = static_unique_ptr_cast_del<Derived, void(*)(Derived*)>(std::move(b));
        d->Do();
    }

    return 0;
}

Note: the dynamic_cast can be replaced by a static_cast in c++20 by adding the constraint: requires std::is_base_of<Base, Derived>::value

Kiangsi answered 27/3, 2024 at 12:56 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.