Home > Software engineering >  Virtual function problem after casting template class instance
Virtual function problem after casting template class instance

Time:09-22

I'm creating a library in C and I would like release some of the objects anytime I want. With raw pointers I couldn't notify the user that the pointer is no longer valid, with shared_ptr I couldn't release the object if the user has her/his own shared_ptr to it. So I decided to write my own smart pointer(ish) class. My goal was to create a class which counts the references and releases the memory if the reference count reaches 0, which is similar to shared_ptr, however it has a destroy method which releases the memory. Also the user can ask it whether or not the memory is still valid (or released).

The pointer_wrapper class contains the raw pointer and the reference count. As I said earlier, it releases the raw pointer if the reference count reaches 0 or if the user calls the destroy method.

template<class T> class pointer_wrapper {
private:
    T* raw_pointer;
    int32_t reference_count = 1;
public:
    pointer_wrapper(T* const raw_pointer): raw_pointer(raw_pointer) { }
    T* get_raw_pointer() const { return raw_pointer; }
    void increase_reference_count() { reference_count  ; }
    void decrease_reference_count() {
        reference_count--;
        if(reference_count == 0) {
            delete this;
        }
    }
    int32_t get_reference_count() const { return reference_count; }
    void destroy() {
        if(raw_pointer != nullptr) {
            delete raw_pointer;
            raw_pointer = nullptr;
        }
    }
    ~pointer_wrapper() { destroy(); }
};

But the pointer_wrapper class is just for internal use, the library's user will always get a ptr instance. The user can copy the ptr object but all the copied ptr objects' pw variable will point to the same pointer_wrapper. This way if I call one of the ptr objects' destroy method, all the other ptr objects' is_valid method will return false. So if the library releases an object, the user will know this if she/he calls the is_valid method before use.

template<class T> class ptr {
private:
    pointer_wrapper<T>* pw;
public:
    ptr(T* const raw_pointer) { pw = new pointer_wrapper<T>(raw_pointer); }
    ptr(pointer_wrapper<T>* const pw): pw(pw) { pw->increase_reference_count(); }
    ptr(const ptr<T>& other_ptr) {
        pw = other_ptr.pw;
        pw->increase_reference_count();
    }
    ptr<T>& operator=(const ptr<T>& other_ptr) {
        pw->decrease_reference_count();
        pw = other_ptr.pw;
        pw->increase_reference_count();
        return *this;
    }
    T* operator->() const { return pw->get_raw_pointer(); }
    int32_t get_reference_count() const { return pw->get_reference_count(); }
    bool is_valid() const { return pw->get_raw_pointer() != nullptr; }

    // the problem is probably here
    template<class X> ptr<X> convert() const { return ptr<X>(reinterpret_cast<pointer_wrapper<X>*>(pw)); }

    void destroy() { pw->destroy(); }
    ~ptr() { pw->decrease_reference_count(); }
};

The general pattern is that I have an exported interface-like class with only pure virtual methods and I have a implementation class (not exported, hidden in the dll) which inherits from the interface-like class.

static ptr<window_system> create() { return ptr<window_system>(new wm_glfw_window_system()); }

This works fine, until I try to cast it by calling the convert method. If I call a method on a converted ptr object, I get an error like this:

Exception thrown at 0x0000000000000000 in example_1.exe: 0xC0000005: Access violation executing location 0x0000000000000000.

ptr<window_system>(new wm_glfw_window_system())->create_window(...); // works fine
ptr<wm_glfw_window_system>(new wm_glfw_window_system())->create_window(...); // works fine
ptr<wm_glfw_window_system>(new wm_glfw_window_system()).convert<window_system>()->create_window(...); // error
ptr<window_system>(new wm_glfw_window_system()).convert<wm_glfw_window_system>()->create_window(...); // error

So I guess I have some problem with that convert method and the reinterpret_cast. But if I'm correct, I can't use other casts because the class ptr<window_system> and ptr<wm_glfw_window_system> are not related even though the window_system and the wm_glfw_window_system classes are related.

So my questions are:

  • Is there a better way of archiving my goals (eg. a library with the appropriate pointer type)?
  • Do my classes make any sense?
  • How can I write my convert method to work properly (it has to support downcasting)?

CodePudding user response:

Don't reinterpet cast.

Replace the single pointer to a pointer wrapper with a pointer to the object and a reference counting helper.

Suppoet static, implicit and dynamic casts in the pointer to object which keep the same reference counter.

CodePudding user response:

with shared_ptr I couldn't release the object if the user has her/his own shared_ptr to it. Is there a better way of archiving my goals (eg. a library with the appropriate pointer type)?

It seems you just have to give std::weak_ptr to your user. If you don't want them to convert and store the std::shared_ptr, you might wrap it in a class, something like:

template <class T> class
pointer_wrapper {
private:
    std::weak_ptr<T> pointer;
public:
    pointer_wrapper(std::weak_ptr<T> pointer): pointer(pointer) {}

    template <typename F>
    bool do_job(F f) {
        if (auto p = pointer.lock()) {
            f(*p);
            return true;
        }
        return false;
    }
    void destroy() {
        do_job([](auto& v){
            // Inform your library to remove the shared_ptr.
            v.release_itself();
        });
    }
};
  • Related