tuket
tuket

Reputation: 3941

Why does this custom pointer class crash?

I am implementing a special purpose handle class.

ihandle is an interface that all handles must implement, in my real code it would have the operator overloadings for -> *. But for this example, I wanted to keep it simple and it just has the get function.

template <typename T>
class ihandle {
public:
    virtual T* get();
};

One possible implementation would be ptr, which is just a raw pointer.

template <typename T>
class ptr : public ihandle<T>
{
    T* t;
public:
    ptr(T* t = nullptr) : t(t) {}
    T* get(){return t;}
};

And then there is handle which which is intended for doing null safety checks.

template <typename T>
class handle
{
public:
    ihandle<T>* h;

    T* get(){return h->get();}
    handle(ihandle<T>* h = nullptr) : h(h) {}

    template <typename D>
    handle(handle<D>& hd)
        : h((ihandle<T>*)hd.h)
    {
        static_assert(is_base_of<T, D>::value, "error");
    }
};

There is this constructor for being able to cast from a handle of an inheriting class to a handle of a base class.

template <typename D>
handle(handle<D>& hd)
    : h((ihandle<T>*)hd.h)
{
    static_assert(is_base_of<T, D>::value, "error");
}

For example, if B inherits from A, I would like to be able to call this function with an instance of handle<B>.

void foo(handle<A> ha) {
    // do something
}

But this gives a segfault for the following sample test.

struct A {
    virtual void talk() {printf("A\n");}
};

struct B : public A {
    void talk() {printf("B\n");}
};

int main()
{
    handle<B> hb(new ptr<B>(new B));
    //hb.get()->talk(); // if uncomment, no segfault
    handle<A> ha = hb;
    ha.get()->talk(); // segfault here

    return 0;
}

I suspect the problem might be in the handle(handle<D>& hd) constructor but I don't understand what's happening.

You can test it by clicking this link: https://onlinegdb.com/BkAYuQZ3z

Upvotes: 3

Views: 72

Answers (2)

Goodies
Goodies

Reputation: 2069

Q: what compiler/linker are you using ? when I compile your stuff using VS-2017, I get a linker error. It sais it expects a B->get() when I include the declaration of hb like in your main test,

handle<B> hb(new ptr<B>(new B));

..but when I provide some default implementation for the virtual method ihandle.get(), like this..

template <typename T>
class ihandle {
   public:
   virtual T* get() { return NULL; }
 };

return NULL.. my linker error is gone.. and there is no crash. You test reports B or B B.

Hope I can help. Thanks for the code ! I'm learning C++ atm, above example is a good study..

Upvotes: 1

user7860670
user7860670

Reputation: 37468

Even if condition is_base_of<T, D>::value is true it does not make (ihandle<T>*) hd.h cast valid because ptr<B> and ihandle<A> types are not related. I guess this is an example of situation where c-style cast should be definitely avoided. To perform conversion safely you can utilize dynamic_cast with a check:

: h(dynamic_cast<ihandle<T>*>(hd.h))
{
   if(hd.h && !h)
   {
       throw ::std::runtime_error{"pointers are not related"};
   }
}

Upvotes: 5

Related Questions