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

时间:2011-11-03 10:11:28

标签: c++

我需要一种方法来验证在编译期间,指向另一个类(派生或基类)的指针的向上/向下转换不会改变指针值。也就是说,演员阵容相当于reinterpret_cast

具体而言,方案如下:我有一个Base类和一个Derived类(显然来自Base)。还有一个模板Wrapper类,它由指向作为模板参数指定的类的指针组成。

class Base
{
    // ...
};

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

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

在某些情况下,我有一个Wrapper<Derived>类型的变量,我想调用一个接收(const)引用ro Wrapper<Base>的函数。显然,此处没有自动投放,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有一个非平凡的代码(你可能已经猜到了),而且我正在使用异常处理。 OTOH如果指向BaseDerived的指针是相同的(如本例所示,因为没有多重继承) - 可以将myWrapper转换为所需类型并调用{{1它会起作用!

因此我将以下内容添加到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不等同于Wrapper<Derived>

我想检测并防止尝试这种无效的向下转发。我添加了验证(如您所见),但它适用于运行时。也就是说,代码将编译并运行,并且在运行时期间,调试版本中将出现崩溃(或失败的断言)。

这很好,但是我想在编译期间捕获这个并且无法构建。一种STATIC_ASSERT。

有没有办法实现这个目标?

1 个答案:

答案 0 :(得分:7)

简答:

答案很长:

在编译时可用的内省有限,例如(使用函数重载决策)可以检测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操纵所有内容,所以我们会通过检查static_cast<T*>std::enable_if中的转化来确保类型安全(以及我们std::is_base_of的有意义的内容)模板构造函数:

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,即使原始Banana为{WrapperRef<Fruit>,您也可以在Wrapper<T>附加Wrapper<Apple> {1}} ...

注意:由于常见的UnkDisposable基类,这很容易!这就是我们的共同点(WrapperImpl)。