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

时间:2015-08-05 11:44:06

标签: c++ incomplete-type template-instantiation

根据一个简单的,侵入式引用计数的对象系统,我有一个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作为其最左边的祖先一样小心,不能虚拟地继承它

,在一些不寻常的平台上,需要一些额外的vtable魔法,这是安全的。

如果我可以在可能的情况下获得额外的static_cast安全层,那么真的会有什么好处。在实践中,T的定义通常通常在实例化Handle::~Handle时完成,这使得它成为仔细检查T实际继承的完美时刻来自CountedBase。如果它不完整,那我就无能为力......但如果它完成了,那么理智检查就会很好。

最后,我们提出了一个问题: 有没有办法进行T继承自CountedBase的编译时检查,这会在T未完成时导致(虚假)错误?

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

1 个答案:

答案 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

Live on Coliru

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

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

话虽如此,我认为更优雅(也更明确)的方法是引入一个类模板incomplete<T>,以便Handle<incomplete<Foo>>编译到reinterpret_cast,以及其他任何内容尝试static_cast