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

Posted

技术标签:

【中文标题】如何在编译时验证 reinterpret_cast 的有效性【英文标题】:How to verify the validity of reinterpret_cast at compile time 【发布时间】:2011-11-03 10:11:27 【问题描述】:

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

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

class Base

    // ...
;

class Derived
    :public Base

    // ...
;

template <class T>
class Wrapper

    T* m_pObj;
    // ...
;

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

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 不同。因此Wrapper&lt;Derived&gt; 不等于Wrapper&lt;Base&gt;

我想检测并阻止这种无效的垂头丧气的尝试。我已经添加了验证(如您所见),但它可以在 run-time 中使用。也就是说,代码会编译并运行,并且在运行时会在调试构建中出现崩溃(或断言失败)。

这很好,但我想在编译时捕捉到它并让构建失败。一种 STATIC_ASSERT。

有没有办法做到这一点?

【问题讨论】:

也许在这里使用static_cast return (TT&amp;) *this; // brute-force case 而不是 c-cast 可能会有所帮助,c-cast 可能正在执行 reinterpret_cast ,这确实会破坏一切。如果两个类共享继承,编译器应该使用static_cast 使指针指向正确的位置。 @RedX:在这个特定的地方,这相当于static_cast,因为rwo 类Wrapper&lt;Base&gt;Wrapped&lt;Derived&gt; 不相关 【参考方案1】:

简短回答:

长答案:

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

不过就是这样。

该标准不需要完全自省,尤其是:

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

当然,无论如何,存在对象布局或多或少未指定的问题(尽管如果我没记错的话,C++11 增加了区分普通布局和具有虚拟方法的类的能力,这有点帮助在这里!)

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

因此,尽管你的主张很大胆 P.S.请不要回复“你为什么要这样做”或“这不符合标准”。我知道这一切是为了什么,我有理由这样做。,你必须调整你的方式。

当然,如果我们能更全面地了解您对本课程的使用情况,我们或许可以集思广益,帮助您找到更好的解决方案。


如何实现类似的系统?

我首先建议一个简单的解决方案:

Wrapper&lt;T&gt; 是所有者类,不可复制,不可转换 WrapperRef&lt;U&gt; 在现有 Wrapper&lt;T&gt; 上实现代理(只要 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&lt;T*&gt; 的意义):

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&lt;Fruit&gt; 附加Banana,即使原来的Wrapper&lt;T&gt;Wrapper&lt;Apple&gt;...

注意:这很容易,因为有共同的UnkDisposable 基类!这就是我们的共同点 (WrapperImpl)。

【讨论】:

非常感谢您的回答。我想到了编译时检查,某种可以在编译时计算并受STATIC_ASSERT 宏影响的表达式(希望您熟悉这种技术)。顺便说一句,我使用的表达式 PBYTE((WrappedType*)(1)) == PBYTE((TT::WrappedType*)(WrappedType*)(1)) 可能理论上是在编译时计算的。我确信在启用优化的发布版本中,编译器将替换已知结果。但是我使用的编译器(msvc)不允许这样做。 更广泛的图片。我有一个基本接口UnkDisposable,它有一个virtual Release() = 0;Wrapper 是一个 RAII 包装器,它包装指向此类对象的指针,并确保在必要时调用 ReleaseWrapper 是一个模板类,它封装了任何继承自 UnkDisposable 的类。类以不同的方式实现UnkDisposable:一些立即删除对象,而另一些实现引用计数(它们也有AddRef)。继续... 有一个函数,它接受一些参数,负责启动一个异步操作。如果成功 - 它应该拥有适当分配资源的所有权。有一个接口CompletionHandler 继承自UnkDisposable,它添加了相关的完成/错误处理方法。该函数将Wrapper&lt;CompletionHandler&gt;&amp; 作为参数。如果一切正常 - 它会将分配的对象从给定的包装器中分离出来并获得它的所有权(将其附加到其内部包装器)。继续... 有一个类MyCompletionHandler 派生自CompletionHandler。它被分配、初始化并传递给函数。我希望能够做到这一点:Wrapper&lt;MyCompletionHandler&gt; pHandler(new MyCompletionHandler);,然后将其用作预期 Wrapper&lt;CompletionHandler&gt;Wrapper&lt;UnkDisposable&gt; 的参数 @valdo:有什么理由不能使用更传统的基于析构函数的 RAII(尤其是智能指针),而不是尝试模拟 C#?

以上是关于如何在编译时验证 reinterpret_cast 的有效性的主要内容,如果未能解决你的问题,请参考以下文章

如何在java中进行reinterpret_cast以获取像素数组[重复]

如何验证编译时在 msvc 2010 c++ 项目中是不是正确设置了编译器选项?

[C/C++]_[初级]_[static_cast,reinterpret_cast,dynimic_cast的使用场景和区别]

重定位图像时如何前进到下一个内存块

为啥不能在常量表达式中使用 reinterpret_cast? [复制]

如何验证scss