可选地 safety-checked 转换为可能不完整的类型
Optionally safety-checked cast on possibly incomplete type
根据一个简单的侵入性reference-counted object 系统,我有一个template<typename T> class Handle
,它是用CountedBase
的子类实例化的。 Handle<T>
持有指向 T
的指针,其析构函数在该指针上调用 DecRef
(在 CountedBase
中定义)。
通常,这会在尝试使用前向声明限制 header 依赖项时导致问题:
#include "Handle.h"
class Foo; // forward declaration
struct MyStruct {
Handle<Foo> foo; // This is okay, but...
};
void Bar() {
MyStruct ms;
} // ...there's an error here, as the implicit ~MyStruct calls
// Handle<Foo>::~Handle(), which wants Foo to be a complete
// type so it can call Foo::DecRef(). To solve this, I have
// to #include the definition of Foo.
作为解决方案,我重写了Handle<T>::~Handle()
如下:
template<typename T>
Handle<T>::~Handle() {
reinterpret_cast<CountedBase*>(m_ptr)->DecRef();
}
请注意,我在这里使用 reinterpret_cast
而不是 static_cast
,因为 reinterpret_cast
不需要 T
的定义是完整的。当然,它也不会为我执行指针调整......但只要我小心布局(T
必须有CountedBase
作为它的最左边的祖先,不能从它虚拟继承,并且在几个不寻常的平台上,一些额外的 vtable 魔法是必要的),它是安全的。
真正 不错的是,如果我能在可能的情况下获得额外的 static_cast
安全层。实际上,T
的定义 通常 在 Handle::~Handle
被实例化时完成,这使得它成为 double-check 的完美时刻 T
其实继承自CountedBase
。如果它不完整,我无能为力......但如果它完整,那么进行完整性检查会很好。
这最终引出了我的问题:
有什么方法可以做 compile-time 检查 T
是否继承自 CountedBase
,当 T
不完整时不会导致(虚假的)错误?
[通常的免责声明:我知道以这种方式使用不完整类型可能存在不安全 and/or UB 方面。尽管如此,经过大量 cross-platform 测试和分析,我确定这是考虑到我用例的某些独特方面的最实用的方法。我对 compile-time 检查问题感兴趣,而不是一般的代码审查。]
在sizeof
上使用SFINAE检查类型是否完整:
struct CountedBase {
void decRef() {}
};
struct Incomplete;
struct Complete : CountedBase {};
template <std::size_t> struct size_tag;
template <class T>
void decRef(T *ptr, size_tag<sizeof(T)>*) {
std::cout << "static\n";
static_cast<CountedBase*>(ptr)->decRef();
}
template <class T>
void decRef(T *ptr, ...) {
std::cout << "reinterpret\n";
reinterpret_cast<CountedBase*>(ptr)->decRef();
}
template <class T>
struct Handle {
~Handle() {
decRef(m_ptr, nullptr);
}
T *m_ptr = nullptr;
};
int main() {
Handle<Incomplete> h1;
Handle<Complete> h2;
}
输出(注意销毁顺序颠倒):
static
reinterpret
尝试使用不是从 CountedBase
派生的完整类型产生:
main.cpp:16:5: error: static_cast from 'Oops *' to 'CountedBase *' is not allowed
话虽这么说,我认为更优雅(也更明确)的方法是引入一个 class 模板 incomplete<T>
,这样 Handle<incomplete<Foo>>
就可以编译成 [=17] =],其他任何尝试 static_cast
.
根据一个简单的侵入性reference-counted object 系统,我有一个template<typename T> class Handle
,它是用CountedBase
的子类实例化的。 Handle<T>
持有指向 T
的指针,其析构函数在该指针上调用 DecRef
(在 CountedBase
中定义)。
通常,这会在尝试使用前向声明限制 header 依赖项时导致问题:
#include "Handle.h"
class Foo; // forward declaration
struct MyStruct {
Handle<Foo> foo; // This is okay, but...
};
void Bar() {
MyStruct ms;
} // ...there's an error here, as the implicit ~MyStruct calls
// Handle<Foo>::~Handle(), which wants Foo to be a complete
// type so it can call Foo::DecRef(). To solve this, I have
// to #include the definition of Foo.
作为解决方案,我重写了Handle<T>::~Handle()
如下:
template<typename T>
Handle<T>::~Handle() {
reinterpret_cast<CountedBase*>(m_ptr)->DecRef();
}
请注意,我在这里使用 reinterpret_cast
而不是 static_cast
,因为 reinterpret_cast
不需要 T
的定义是完整的。当然,它也不会为我执行指针调整......但只要我小心布局(T
必须有CountedBase
作为它的最左边的祖先,不能从它虚拟继承,并且在几个不寻常的平台上,一些额外的 vtable 魔法是必要的),它是安全的。
真正 不错的是,如果我能在可能的情况下获得额外的 static_cast
安全层。实际上,T
的定义 通常 在 Handle::~Handle
被实例化时完成,这使得它成为 double-check 的完美时刻 T
其实继承自CountedBase
。如果它不完整,我无能为力......但如果它完整,那么进行完整性检查会很好。
这最终引出了我的问题:
有什么方法可以做 compile-time 检查 T
是否继承自 CountedBase
,当 T
不完整时不会导致(虚假的)错误?
[通常的免责声明:我知道以这种方式使用不完整类型可能存在不安全 and/or UB 方面。尽管如此,经过大量 cross-platform 测试和分析,我确定这是考虑到我用例的某些独特方面的最实用的方法。我对 compile-time 检查问题感兴趣,而不是一般的代码审查。]
在sizeof
上使用SFINAE检查类型是否完整:
struct CountedBase {
void decRef() {}
};
struct Incomplete;
struct Complete : CountedBase {};
template <std::size_t> struct size_tag;
template <class T>
void decRef(T *ptr, size_tag<sizeof(T)>*) {
std::cout << "static\n";
static_cast<CountedBase*>(ptr)->decRef();
}
template <class T>
void decRef(T *ptr, ...) {
std::cout << "reinterpret\n";
reinterpret_cast<CountedBase*>(ptr)->decRef();
}
template <class T>
struct Handle {
~Handle() {
decRef(m_ptr, nullptr);
}
T *m_ptr = nullptr;
};
int main() {
Handle<Incomplete> h1;
Handle<Complete> h2;
}
输出(注意销毁顺序颠倒):
static
reinterpret
尝试使用不是从 CountedBase
派生的完整类型产生:
main.cpp:16:5: error: static_cast from 'Oops *' to 'CountedBase *' is not allowed
话虽这么说,我认为更优雅(也更明确)的方法是引入一个 class 模板 incomplete<T>
,这样 Handle<incomplete<Foo>>
就可以编译成 [=17] =],其他任何尝试 static_cast
.