如何在编译时验证reinterpret_cast的有效性

How to verify the validity of reinterpret_cast at compile time

本文关键字:cast 有效性 reinterpret 验证 编译      更新时间:2023-10-16

我需要一种方法来验证在编译时指向另一个类(派生类或基类(的指针的上/下转换不会更改指针值。也就是说,强制转换等效于reinterpret_cast

具体来说,场景如下:我有一个Base类和一个Derived类(显然是从Base派生而来(。还有一个模板Wrapper类,它包含一个指向指定为模板参数的类的指针。

class Base
{
    // ...
};
class Derived
    :public Base
{
    // ...
};
template <class T>
class Wrapper
{
    T* m_pObj;
    // ...
};

在某些情况下,我有一个类型为Wrapper<Derived>的变量,我想调用一个函数,该函数接收对Wrapper<Base>的(const(引用。显然,这里没有自动转换,Wrapper<Derived>不是从Wrapper<Base>派生的。

void SomeFunc(const Wrapper<Base>&);
Wrapper<Derived> myWrapper;
// ...
SomeFunc(myWrapper); // compilation error here

有一些方法可以在标准C++的范围内处理这种情况。例如:

Derived* pDerived = myWrapper.Detach();
Wrapper<Base> myBaseWrapper;
myBaseWrapper.Attach(pDerived);
SomeFunc(myBaseWrapper);
myBaseWrapper.Detach();
myWrapper.Attach(pDerived);

但我不喜欢这样。这不仅需要一个笨拙的语法,而且还产生了一个额外的代码,因为Wrapper有一个非平凡的d’tor(正如你可能猜到的(,而我使用的是异常处理。OTOH如果指向BaseDerived的指针相同(就像本例中一样,因为没有多重继承(-可以将myWrapper强制转换为所需的类型并调用SomeFunc,它就可以工作了!

因此,我在Wrapper中添加了以下内容:

template <class T>
class Wrapper
{
    T* m_pObj;
    // ...
    typedef T WrappedType;

    template <class TT>
    TT& DownCast()
    {
        const TT::WrappedType* p = m_pObj; // Ensures GuardType indeed inherits from TT::WrappedType
        // The following will crash/fail if the cast between the types is not equivalent to reinterpret_cast
        ASSERT(PBYTE((WrappedType*)(1)) == PBYTE((TT::WrappedType*)(WrappedType*)(1)));
        return (TT&) *this; // brute-force case
    }
    template <class TT> operator const Wrapper<TT>& () const
    {
        return DownCast<Wrapper<TT> >();
    }
};

Wrapper<Derived> myWrapper;
// ...
// Now the following compiles and works:
SomeFunc(myWrapper);

问题是,在某些情况下,暴力强制转换是无效的。例如,在这种情况下:

class Base
{
    // ...
};
class Derived
    :public AnotherBase
    ,public Base
{
    // ...
};

这里,指向Base的指针的值不同于Derived。因此CCD_ 18并不等同于CCD_。

我想检测并阻止这种无效的向下转换的尝试。我已经添加了验证(正如您可能看到的(,但它在运行时中有效。也就是说,代码将编译并运行,在运行时,调试构建中将出现崩溃(或断言失败(。

这很好,但我希望在编译时发现这一点,并使构建失败。一种STATIC_ASERT。

有办法做到这一点吗?

简短回答:

长答案:

编译时可用的内省是有限的,例如,您可以(使用函数重载解析(检测类B是否是另一类D的可访问基类。

然而,仅此而已。

该标准不需要全面反思,特别是:

  • 不能列出类的(直接(基类
  • 你无法知道一个类是只有一个基类还是几个基类
  • 你甚至不知道基类是否是第一个基类

当然,无论如何,对象布局或多或少都是未指定的(尽管如果我没记错的话,C++11增加了区分琐碎布局和使用虚拟方法的类的能力,这在这里有点帮助!(

使用Clang及其AST检查功能,我认为您可以编写一个专用的检查器,但这似乎相当复杂,当然是完全不可移植的。

因此,尽管你大胆声称p.S.请不要回复"你为什么要这样做"或"这违反了标准"。我知道这一切是为了什么,我有理由这么做,你将不得不调整你的方式。

当然,如果我们能更全面地了解你对这门课的使用情况,我们可能会集思广益,帮助你找到更好的解决方案。


如何实施类似的系统?

首先,我提出一个简单的解决方案:

  • Wrapper<T>是所有者类,不可复制,不可转换
  • WrapperRef<U>在现有的Wrapper<T>上实现代理(只要T*可转换为U*(并提供转换设施

我们将使用这样一个事实,即所有要操作的指针都继承自UnkDisposable(这是一个关键信息!(

代码:

namespace details {
  struct WrapperDeleter {
    void operator()(UnkDisposable* u) { if (u) { u->Release(); } }
  };

  typedef std::unique_ptr<UnkDisposable, WrapperDeleter> WrapperImpl;
}
template <typename T>
class Wrapper {
public:
  Wrapper(): _data() {}
  Wrapper(T* t): _data(t) {}
  Wrapper(Wrapper&& right): _data() {
    using std::swap;
    swap(_data, right._data);
  }
  Wrapper& operator=(Wrapper&& right) {
    using std::swap;
    swap(_data, right._data);
    return *this;
  }
  T* Get() const { return static_cast<T*>(_data.get()); }
  void Attach(T* t) { _data.reset(t); }
  void Detach() { _data.release(); }
private:
  WrapperImpl _data;
}; // class Wrapper<T>

既然我们奠定了基础,我们就可以制作我们的自适应代理了。因为我们将只通过WrapperImpl操作所有内容,所以我们通过检查模板构造函数中通过std::enable_ifstd::is_base_of的转换来确保类型安全性(以及static_cast<T*>的有意义性(:

template <typename T>
class WrapperRef {
public:
  template <typename U>
  WrapperRef(Wrapper<U>& w,
    std::enable_if_c< std::is_base_of<T, U> >::value* = 0):
    _ref(w._data) {}
  // Regular
  WrapperRef(WrapperRef&& right): _ref(right._ref) {}
  WrapperRef(WrapperRef const& right): _ref(right._ref) {}
  WrapperRef& operator=(WrapperRef right) {
    using std::swap;
    swap(_ref, right._ref);
    return *this;
  }
  // template
  template <typename U>
  WrapperRef(WrapperRef<U>&& right,
    std::enable_if_c< std::is_base_of<T, U> >::value* = 0):
    _ref(right._ref) {}
  template <typename U>
  WrapperRef(WrapperRef<U> const& right,
    std::enable_if_c< std::is_base_of<T, U> >::value* = 0):
    _ref(right._ref) {}
  T* Get() const { return static_cast<T*>(_ref.get()); }
  void Detach() { _ref.release(); }
private:
  WrapperImpl& _ref;
}; // class WrapperRef<T>

它可能会根据您的需要进行调整,例如,您可以取消复制和移动WrapperRef类的功能,以避免它指向不再有效的Wrapper

另一方面,您也可以使用shared_ptr/weak_ptr方法来丰富这一点,以便能够复制和移动包装器,并且仍然保证可用性(但要注意内存泄漏(。

注意:WrapperRef不提供Attach方法是有意的,这样的方法不能与基类一起使用。否则,如果AppleBanana都源自Fruit,则可以通过WrapperRef<Fruit>连接Banana,即使原始Wrapper<T>Wrapper<Apple>

注意:这很容易,因为有通用的UnkDisposable基类!这给了我们一个共同的分母(WrapperImpl(