可选择安全检查铸造可能不完整的类型

Sne*_*tel 5 c++ incomplete-type template-instantiation

根据一个简单的,侵入式引用计数的对象系统,我有一个template<typename T> class Handle,它意味着用子类实例化CountedBase.Handle<T>拥有一个指向a的指针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.
Run Code Online (Sandbox Code Playgroud)

作为解决方案,我重写Handle<T>::~Handle()如下:

template<typename T>
Handle<T>::~Handle() {
    reinterpret_cast<CountedBase*>(m_ptr)->DecRef();
}
Run Code Online (Sandbox Code Playgroud)

请注意,我在reinterpret_cast这里使用而不是static_cast,因为reinterpret_cast不需要T完成定义.当然,它也不会为我执行指针调整...但只要我对布局很谨慎(T必须有CountedBase最左边的祖先,不能虚拟地继承它,并且在几个不寻常的平台上,一些额外的vtable魔法是必要的),这是安全的.

但是,如果我可以在可能的情况下获得额外的安全层,那将会是非常好的static_cast.在实践中,该定义T通常在哪里点完全Handle::~Handle被实例化,使其成为一个完美的时刻重复检查T实际上是从继承CountedBase.如果它不完整,那我就无能为力......但如果它完成了,那么理智检查就会很好.

这给我们带来了,终于,我的问题: 有没有办法做一个编译时的支票,T从继承CountedBase,这将不会导致(假)错误时T不完整?

[通常的免责声明:我知道以这种方式使用不完整类型可能存在不安全和/或UB方面的问题.然而,经过大量的跨平台测试和分析后,我已经确定这是我用例的某些独特方面最实用的方法.我对编译时检查问题很感兴趣,而不是一般的代码审查.]

Que*_*tin 2

使用 SFINAE onsizeof检查类型是否完整:

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;
}
Run Code Online (Sandbox Code Playgroud)

输出(注意破坏的顺序是相反的):

static
reinterpret
Run Code Online (Sandbox Code Playgroud)

住在科利鲁

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

main.cpp:16:5: error: static_cast from 'Oops *' to 'CountedBase *' is not allowed
Run Code Online (Sandbox Code Playgroud)

话虽这么说,我认为更优雅(也更明确)的方法是引入一个类模板incomplete<T>,这样就可以Handle<incomplete<Foo>>编译为reinterpret_cast,而其他任何东西都会尝试编译为static_cast