铸造模板 class 实例后的虚函数问题

Virtual function problem after casting template class instance

我正在用 C++ 创建一个库,我想随时释放一些对象。使用原始指针我无法通知用户指针不再有效,使用 shared_ptr 如果用户拥有 her/his 拥有的 shared_ptr 我无法释放该对象。所以我决定编写自己的智能指针(ish)class。我的目标是创建一个 class 来计算引用并在引用计数达到 0 时释放内存,这类似于 shared_ptr,但是它有一个释放内存的 destroy 方法.用户也可以询问内存是否仍然有效(或释放)。

pointer_wrapper class 包含原始指针和引用计数。正如我之前所说,如果引用计数达到 0 或用户调用 destroy 方法,它会释放原始指针。

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(); }
};

但是 pointer_wrapper class 仅供内部使用,库的用户将始终获得一个 ptr 实例。用户可以复制 ptr 对象,但所有复制的 ptr 对象的 pw 变量将指向同一个 pointer_wrapper。这样,如果我调用 ptr 对象之一的 destroy 方法,所有其他 ptr 对象的 is_valid 方法将 return false。因此,如果库释放了一个对象,如果 she/he 在使用前调用 is_valid 方法,用户将知道这一点。

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(); }
};

一般模式是我有一个类似导出接口的 class,只有纯虚拟方法,我有一个实现 class(未导出,隐藏在 dll 中)继承自类似界面 class.

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

这工作正常,直到我尝试通过调用 convert 方法来转换它。如果我在转换后的 ptr 对象上调用方法,我会收到如下错误:

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

所以我想 convert 方法和 reinterpret_cast 有一些问题。但如果我是正确的,我不能使用其他演员表,因为 class ptr<window_system>ptr<wm_glfw_window_system> 不相关,即使 window_systemwm_glfw_window_system class这些是相关的。

所以我的问题是:

不要重复转换。

用指向对象的指针和引用计数助手替换指向包装器的单个指针。

支持在保持相同引用计数的对象指针中进行静态、隐式和动态转换。

(这就是 shared_ptr 的大致工作方式)。

除了您的代码表现出未定义的行为之外,您的问题是指向 base 和 derived 的指针不需要具有相同的值。

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)?

看来您只需要向您的用户提供 std::weak_ptr。 如果您不希望他们转换和存储 std::shared_ptr,您可以将其包装在 class 中,例如:

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();
        });
    }
};