5

根据一个简单的、侵入式引用计数的对象系统,我有一个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.

作为解决方案,我重写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. 如果它不完整,我无能为力……但如果它是完整的,那么进行健全性检查会很好。

最后,这给我们带来了我的问题: 是否有任何方法可以进行编译时检查,当未完成TCountedBase不会导致(虚假)错误?T

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

4

1 回答 1

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;
}

输出(注意破坏顺序是颠倒的):

static
reinterpret

住在科利鲁

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

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

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

于 2015-08-05T12:11:12.570 回答