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

标签 c++

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

具体来说,场景如下:我有一个 Base类和一个Derived类(显然派生自 Base )。还有一个模板Wrapper由指向指定为模板参数的类的指针组成的类。

class Base
{
    // ...
};

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

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

在某些情况下,我有一个 Wrapper<Derived> 类型的变量,我想调用一个接收(常量)引用 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有一个重要的 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<Derived>不等于 Wrapper<Base> .

我想检测并防止这种无效的沮丧尝试。我已经添加了验证(如您所见),但它在运行时 中有效。也就是说,代码将编译并运行,并且在运行时调试构建中会出现崩溃(或断言失败)。

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

有什么办法可以实现吗?

最佳答案

简短回答:

长答案:

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

但仅此而已。

该标准不需要完全自省(introspection),特别是:

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

当然,无论如何,对象布局或多或少是未指定的问题(尽管 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> ...

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

关于c++ - 如何在编译时验证reinterpret_cast的有效性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7993128/

相关文章:

android - openfl - 音频不适用于 cpp 目标

C++:如何从 make_shared 部分推导模板参数

c++ - 它被添加到具有相同键的 hash_mapp 字符串

c++ - 大量相同纹理的四边形

c++ - auto stdMaxInt = std::max<int> 的类型推导失败;

c++ - 记录函数调用的工具?

c++ - linux 上的 c++ 交叉编译到 windows

c++ - 模仿结构/类之间的 C#/D 差异是否是好的 C++ 风格?

c++ - 在 C++ 中使用 Rust 库时 undefined reference

c++ - 需要帮助通过引用类传递 Windows 事件句柄