【发布时间】: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<window_system> 和 ptr<wm_glfw_window_system> 类是不相关的,即使 window_system 和 wm_glfw_window_system 类是相关的。
所以我的问题是:
- 是否有更好的方法来归档我的目标(例如,具有适当指针类型的库)?
- 我的课有意义吗?
- 如何编写
convert方法才能正常工作(它必须支持向下转换)?
【问题讨论】:
-
“我不能使用其他演员表” → 你也不能使用
reinterpret_cast。唯一的区别是编译器会让你这样做。您的程序仍然会出错,并且会出现崩溃或其他有趣的结果。 -
如果代码 A 正在使用指针,而代码 B 销毁指针会发生什么?
-
据我了解,您必须对内部指针类型而不是包装器执行
dynamic_cast。您必须知道,在 C++ 中,当您执行static_cast或dynamic_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