【问题标题】:Virtual function problem after casting template class instance转换模板类实例后的虚函数问题
【发布时间】:2021-11-15 11:39:08
【问题描述】:

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

pointer_wrapper 类包含原始指针和引用计数。正如我之前所说,如果引用计数达到 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 类仅供内部使用,库的用户将始终获得ptr 实例。用户可以复制ptr 对象,但所有复制的ptr 对象的pw 变量将指向同一个pointer_wrapper。这样,如果我调用其中一个ptr 对象的destroy 方法,所有其他ptr 对象的is_valid 方法将返回false。所以如果库释放了一个对象,用户在使用前调用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(); }
};

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

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

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

example_1.exe 中的 0x0000000000000000 处引发异常:0xC0000005:访问冲突执行位置 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 有一些问题。但如果我是正确的,我不能使用其他类型转换,因为 ptr&lt;window_system&gt;ptr&lt;wm_glfw_window_system&gt; 类是不相关的,即使 window_systemwm_glfw_window_system 类是相关的。

所以我的问题是:

  • 是否有更好的方法来归档我的目标(例如,具有适当指针类型的库)?
  • 我的课有意义吗?
  • 如何编写convert 方法才能正常工作(它必须支持向下转换)?

【问题讨论】:

  • “我不能使用其他演员表” → 你也不能使用reinterpret_cast。唯一的区别是编译器会让你这样做。您的程序仍然会出错,并且会出现崩溃或其他有趣的结果
  • 如果代码 A 正在使用指针,而代码 B 销毁指针会发生什么?
  • 据我了解,您必须对内部指针类型而不是包装器执行dynamic_cast。您必须知道,在 C++ 中,当您执行 static_castdynamic_cast 时,编译器会调整指针。 reinterpret_cast 几乎只能用于转换为临时类型,然后转换回精确的原始类型,主要用于使用允许传递用户定义的指针的 API...对于特定的编译器,可能有一些reinterpret_cast 的其他用途,但它们可能不可移植。
  • 也许自定义删除器和(std::shared_ptr)/或std::weak_ptr是你想要的东西
  • spectras,Phil1970:好吧,所以使用reinterpret_cast 不是一个好主意,Yakk - Adam Nevraumont:就我而言,这不可能真的发生,Phil1970:但如果我是对的, 这个删除器只会在最后一个shared_ptr被销毁时调用

标签: c++ templates casting virtual-functions reinterpret-cast


【解决方案1】:

不要重复演员表。

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

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

(这大致就是 shared_ptr 的工作原理)。

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

【讨论】:

    【解决方案2】:

    使用shared_ptr,如果用户拥有她/他自己的shared_ptr,我无法释放该对象。 有没有更好的方法来归档我的目标(例如,具有适当指针类型的库)?

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

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

    【讨论】:

    • 这是个好主意,但是实现release_itself方法可能会很麻烦,所以我更喜欢Yakk - Adam Nevraumont的想法。
    • 也许存储在库中的std::shared_ptr 上的(观察者)指针可能就足够了,在不知道如何处理std::shared_ptr 的生命周期的情况下很难走得更远。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多