我需要一种方法来在编译时验证 指向另一个类(派生类或基类)的指针的向上/向下转换不会改变指针值。也就是说,类型转换等价于 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 有一个非常重要的 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 的可访问基类。

然而,仅此而已。

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

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

  • 当然,无论如何,存在对象布局或多或少未指定的问题(尽管如果我没记错的话,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 ,即使原始 BananaWrapperRef<Fruit> ,您也可以通过 Wrapper<T> 附加 Wrapper<Apple> ...

    注意:这很容易,因为有共同的 UnkDisposable 基类!这就是给我们一个公分母( WrapperImpl )的原因。

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

    10-11 22:39
    查看更多