【问题标题】:Any reason to prefer static_cast over a chain of implicit conversions?有什么理由比隐式转换链更喜欢 static_cast 吗?
【发布时间】:2011-06-24 16:50:19
【问题描述】:

假设我有一个实现多个接口的类

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;

看起来这将解决两个问题 - 指针调整和无效向下转换的风险。

第二种方案有什么问题吗?喜欢第一个的原因可能是什么?

【问题讨论】:

  • 有趣的是CMyClass 在这里知道CDerivedClass...不是不可能,甚至不是糟糕设计的真正迹象,但在一般情况下,CMyClass 不应该有任何知识的后代。我可以想象在头文件和定义CMyClass方法的翻译单元中定义这两个类,包括两者,在VS中更是如此,因为它倾向于提倡预编译的头文件......仍然值得深思。

标签: c++ casting


【解决方案1】:

你可以使用这个模板:

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&lt;B*&gt;,请相应调整模板。

【讨论】:

  • 这就是所谓的“向上转型”——基类通常绘制在顶部,派生在底部。
【解决方案2】:

分配给 void* 总是不安全的。无论你用哪种方式编写它都可能搞砸 - 假设用户正在尝试对 Interface1 进行 QI,那么以下都不是警告或错误:

Interface2* interfacePointer = this;
void* pointer = interfacePointer;

void* pointer = static_cast<Interface2*>( this );

考虑到意外使用 static_cast 进行向上转换的风险很小,在一个很可能甚至无法访问派生类定义的文件中,我看到了很多额外的努力却几乎没有实际安全性。

【讨论】:

  • 对我来说,两行而不是一行的“付出了很多额外的努力”似乎有点夸张。
  • 我认为你应该把努力理解为努力阅读,而不是努力写作。
【解决方案3】:

我看不出有任何理由不使用后一种解决方案,除了这样一个事实,如果其他人正在阅读您的代码,它不会立即传达您为什么使用如此复杂的语句(“为什么他不是只是使用 static_cast ?!?”),所以最好评论它或使意图非常清楚。

【讨论】:

  • 使用其他答案中的模板建议之一,既安全又清晰。
  • 感觉还是少了点什么,但至少更简洁了。
【解决方案4】:

你的分析在我看来是正确的。不使用您的隐式方法的原因并不令人信服:

  • 稍微详细一点
  • 让变量悬而未决
  • static_cast 可以说更常见,因此对其他开发人员来说更明显,搜索等。
  • 在许多情况下,甚至派生类的声明也不会出现在基类函数的定义之前,因此不会出现此类错误

【讨论】:

    【解决方案5】:

    如果您害怕使用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);
    }
    

    【讨论】:

    • dynamic_cast 是浪费时间(大约 2000 个周期),并且在执行通过该代码之前不会发现愚蠢的错误。是的,总比没有好,但编译时检查通常比等效的运行时检查要好。
    猜你喜欢
    • 2011-02-25
    • 2016-06-29
    • 1970-01-01
    • 1970-01-01
    • 2011-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-23
    相关资源
    最近更新 更多