可选地 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

Live on Coliru

尝试使用不是从 CountedBase 派生的完整类型产生:

main.cpp:16:5: error: static_cast from 'Oops *' to 'CountedBase *' is not allowed

话虽这么说,我认为更优雅(也更明确)的方法是引入一个 class 模板 incomplete<T>,这样 Handle<incomplete<Foo>> 就可以编译成 [=17] =],其他任何尝试 static_cast.