如何在编译时验证reinterpret_cast的有效性
How to verify the validity of reinterpret_cast at compile time
我需要一种方法来验证在编译时指向另一个类(派生类或基类(的指针的上/下转换不会更改指针值。也就是说,强制转换等效于reinterpret_cast
。
具体来说,场景如下:我有一个Base
类和一个Derived
类(显然是从Base
派生而来(。还有一个模板Wrapper
类,它包含一个指向指定为模板参数的类的指针。
class Base
{
// ...
};
class Derived
:public Base
{
// ...
};
template <class T>
class Wrapper
{
T* m_pObj;
// ...
};
在某些情况下,我有一个类型为Wrapper<Derived>
的变量,我想调用一个函数,该函数接收对Wrapper<Base>
的(const(引用。显然,这里没有自动转换,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如果指向Base
和Derived
的指针相同(就像本例中一样,因为没有多重继承(-可以将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
。因此CCD_ 18并不等同于CCD_。
我想检测并阻止这种无效的向下转换的尝试。我已经添加了验证(正如您可能看到的(,但它在运行时中有效。也就是说,代码将编译并运行,在运行时,调试构建中将出现崩溃(或断言失败(。
这很好,但我希望在编译时发现这一点,并使构建失败。一种STATIC_ASERT。
有办法做到这一点吗?
简短回答:否。
长答案:
编译时可用的内省是有限的,例如,您可以(使用函数重载解析(检测类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
操作所有内容,所以我们通过检查模板构造函数中通过std::enable_if
和std::is_base_of
的转换来确保类型安全性(以及static_cast<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>
它可能会根据您的需要进行调整,例如,您可以取消复制和移动WrapperRef
类的功能,以避免它指向不再有效的Wrapper
。
另一方面,您也可以使用shared_ptr
/weak_ptr
方法来丰富这一点,以便能够复制和移动包装器,并且仍然保证可用性(但要注意内存泄漏(。
注意:WrapperRef
不提供Attach
方法是有意的,这样的方法不能与基类一起使用。否则,如果Apple
和Banana
都源自Fruit
,则可以通过WrapperRef<Fruit>
连接Banana
,即使原始Wrapper<T>
是Wrapper<Apple>
注意:这很容易,因为有通用的UnkDisposable
基类!这给了我们一个共同的分母(WrapperImpl
(
- 如何理解C++标准N3337中的expr.const.cast子句8
- C++Cast运算符过载
- 如何检查类中共享指针的有效性?
- 列表的有效性在插入后开始迭代器
- 错误:"cast"未命名类型void setCastDescription(std::string
- 通过使用 const-cast 的非常量引用来延长临时的寿命
- "(void) cast"与功能有什么区别 "__attributes__"来沉默未使用的参数警告?
- C++:"Expected '(' for function-style cast or type construction"错误
- lambda 函数返回值的有效性
- 提升图形库:以高性能的方式检查vertex_descriptor的有效性
- 没有迭代器失效是否意味着推进迭代器的有效性?
- 运算符返回的指针的有效性 >
- 为什么选择 g++ 给予者:"error: cast to pointer from integer of different size [-Werror=int-to-pointer-cast]"
- Gtk+ g_signal_connect() 和 C++ lambda 会导致"invalid cast"错误
- Shared_ptr cast vs static_cast speed
- QUdpSocket 失去有效性
- QTreeWidgetItem 析构函数和 QTreeWidgetItemIterator 有效性
- 模板化代码的有效性是什么意思
- 在 iOS 上使用 Aruco 构建 OpenCV 时"Functional-style cast from id to double is not allowed"
- 覆盖 CAST 运算符(我认为它被称为向下转换)