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

C++ 如何在编译时验证reinterpret_cast的有效性,c++,C++,我需要一种方法在编译时验证指向另一个类(派生类或基类)的指针的上/下转换不会更改指针值。也就是说,强制转换相当于重新解释强制转换 具体来说,场景如下:我有一个Base类和一个派生的类(显然派生自Base)。还有一个templateWrapper类,它由指向指定为模板参数的类的指针组成 class Base { // ... }; class Derived :public Base { // ... }; template <class T> class

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

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

class Base
{
    // ...
};

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

template <class T>
class Wrapper
{
    T* m_pObj;
    // ...
};
问题是,在某些情况下,暴力施法是无效的。例如,在这种情况下:

class Base
{
    // ...
};

class Derived
    :public AnotherBase
    ,public Base
{
    // ...
};
此处指向
Base
的指针的值不同于
Derived
。因此,
Wrapper
并不等同于
Wrapper

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

这很好,但我希望在编译时捕捉到这一点,并使构建失败。一种静态的断言


有办法做到这一点吗?

简单回答:没有

长答覆:

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

不过,就是这样

本标准不要求进行全面反思,尤其是:

  • 不能列出类的(直接)基类
  • 您无法知道一个类是只有一个基类还是几个基类
  • 您甚至无法知道基类是否是第一个基类
当然,还有一个问题,即对象布局或多或少是未指定的(尽管C++11添加了区分普通布局和虚拟方法类的能力,如果我没记错的话,这在这里有点帮助!)

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

因此,尽管您提出了大胆的要求,请不要回答“您为什么要这样做”或“这违反了标准”。我知道这一切是为了什么,我有我这样做的理由,你必须调整你的方式

当然,如果我们能更全面地了解您对该类的使用情况,我们也许能够集中精力,帮助您找到更好的解决方案


如何实现类似的系统

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

  • Wrapper
    是所有者类,不可复制,不可转换
  • WrapperRef
    在现有的
    Wrapper
    上实现代理(只要
    T*
    可转换为
    U*
    ),并提供转换功能
我们将使用这样一个事实,即所有要操作的指针都继承自
(这是一个关键信息!)

代码:

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

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

注意:
WrapperRef
不提供
Attach
方法是有意的,这样的方法不能与基类一起使用。否则,对于源自
水果的
苹果
香蕉
,您可以通过
WrapperRef
附加
香蕉
,即使原始
包装
包装


注意:这很容易,因为有一个通用的
基类!这就是我们的共同点(
wrapperpimpl
)。

可能在这里使用
static\u cast
返回(TT&)*This;//暴力案例
c-cast可能会有所帮助,而c-cast可能会重新解释cast,这确实会破坏一切。如果两个类都共享继承,编译器应该使用
static\u cast
@RedX使指针指向正确的位置:在这个特定的位置,这相当于
static\u cast
,因为rwo类
包装器
包装器
不相关,非常感谢您的回答。我考虑过编译时检查,这是一种表达式,可以在编译时进行计算,并使用
STATIC\u ASSERT
宏(希望您熟悉这项技术)。顺便说一句,我使用的表达式PBYTE((WrappedType*)(1))==PBYTE((TT::WrappedType*)(WrappedType*)(1))
理论上都可以在编译时计算。我确信在启用了优化的发布版本中,编译器将替换已知的结果。然而,我使用的编译器(msvc)不允许这种情况。更广泛的情况。我有一个基本接口
unk
,它有一个
虚拟发布()=0
Wrapper
是一个RAII包装器,它包装指向此类对象的指针,并确保在必要时调用
Release
<代码>包装器
是一个模板类,它包装从
继承的任何类
。类以不同的方式实现
AddRef
:一些类立即删除对象,而另一些类实现引用计数(它们也有
AddRef
)。继续…有一个函数接受一些参数并负责启动异步操作。如果这一点成功了,那它就失败了
Derived* pDerived = myWrapper.Detach();

Wrapper<Base> myBaseWrapper;
myBaseWrapper.Attach(pDerived);

SomeFunc(myBaseWrapper);

myBaseWrapper.Detach();
myWrapper.Attach(pDerived);
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
{
    // ...
};
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>
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>