根据一个简单的,侵入式引用计数的对象系统,我有一个template<typename T> class Handle
,它意味着用CountedBase
的子类进行实例化。 Handle<T>
包含指向T
的指针,其析构函数在该指针上调用DecRef
(在CountedBase
中定义)。
通常,当尝试使用前向声明限制标头依赖项时,这会导致问题:
#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
作为其最左边的祖先一样小心,不能虚拟地继承它
如果我可以在可能的情况下获得额外的static_cast
安全层,那么真的会有什么好处。在实践中,T
的定义通常通常在实例化Handle::~Handle
时完成,这使得它成为仔细检查T
实际继承的完美时刻来自CountedBase
。如果它不完整,那我就无能为力......但如果它完成了,那么理智检查就会很好。
最后,我们提出了一个问题:
有没有办法进行T
继承自CountedBase
的编译时检查,这会在T
未完成时导致(虚假)错误?
[通常免责声明:我知道以这种方式使用不完整类型可能存在不安全和/或UB方面的问题。然而,经过大量的跨平台测试和分析后,我已经确定这是我用例的某些独特方面最实用的方法。我对编译时检查问题很感兴趣,而不是一般的代码审查。]
答案 0 :(得分:2)
在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
话虽如此,我认为更优雅(也更明确)的方法是引入一个类模板incomplete<T>
,以便Handle<incomplete<Foo>>
编译到reinterpret_cast
,以及其他任何内容尝试static_cast
。