假设我有一个实现多个接口的类
class CMyClass : public IInterface1, public IInterface2 { };
并且在该类的成员函数中,我需要获得指向其中一个接口的void*
指针(IUnknown::QueryInterface()
中的典型情况。
典型的解决方案是使用static_cast
来实现指针调整:
void* pointer = static_cast<IInterface2*>( this );
如果没有从CMyClass
继承的已知类,则在这种情况下是安全的。但如果存在这样的类:
class CDerivedClass : public CUnrelatedClass, public CMyClass {};
我意外地做了
void* pointer = static_cast<CDerivedClass*>( this );
和this
实际上是一个指向编译器不会抓住我的CMyClass
实例的指针,程序可能会在以后遇到未定义的行为 - static_cast
变得不安全。
建议的解决方案是使用隐式转换:
IInterface2* interfacePointer = this;
void* pointer = interfacePointer;
看起来这样可以解决这两个问题 - 指针调整和无效向下转发的风险。
第二种方案有问题吗?可能有什么理由更喜欢第一个?
答案 0 :(得分:3)
您可以使用此模板:
template<class T, class U> T* up_cast(U* p) { return p; }
用法:
struct B {};
struct C : B {};
int main()
{
C c;
void* b = up_cast<B>(&c);
}
请注意,'*'是隐含的。如果您更喜欢up_cast<B*>
,请相应调整模板。
答案 1 :(得分:2)
分配给void *总是不安全的。无论你怎么写它都会搞砸 - 假设用户试图为Interface1做QI,那么下面的任何一个都不会是警告或错误:
Interface2* interfacePointer = this;
void* pointer = interfacePointer;
或
void* pointer = static_cast<Interface2*>( this );
鉴于意外使用static_cast进行强制转换的风险很小,在一个很可能甚至无法访问派生类定义的文件中,我看到了很多额外的努力,实际安全性很小。
答案 2 :(得分:2)
除了以下事实之外我没有任何理由不使用后一种解决方案,如果其他人正在阅读您的代码,它将立即与您沟通,因为您使用了这样一个错综复杂的陈述(“为什么不是他只是使用static_cast?!?“),所以最好对它进行评论或使意图非常清晰。
答案 3 :(得分:1)
您的分析对我来说听起来很合理。不使用隐式方法的原因并不引人注目:
答案 4 :(得分:1)
如果您害怕偶然使用static_cast
做某事,那么我建议您将转换/界面指针获取到一些模板函数中,例如:像这样:
template <typename Interface, typename SourceClass>
void set_if_pointer (void * & p, SourceClass * c)
{
Interface * ifptr = c;
p = ifptr;
}
或者,使用dynamic_cast
并检查NULL
指针值。
template <typename Interface, typename SourceClass>
void set_if_pointer (void * & p, SourceClass * c)
{
p = dynamic_cast<Interface *>(c);
}