这样一个垂头丧气的人安全吗

Is such a downcast safe?

本文关键字:垂头丧气 安全 一个      更新时间:2023-10-16

假设我们有以下代码:

#include <memory>
#include <vector>
struct BaseComponent
{
    template <typename T>
    T * as()
    {
        return static_cast<T*>(this);
    }
    virtual ~BaseComponent() {}
};
template <typename T>
struct Component : public BaseComponent
{
    virtual ~Component() {}
};
struct PositionComponent : public Component<PositionComponent>
{
    float x, y, z;
    virtual ~PositionComponent() {}
};
int main()
{
    std::vector<std::unique_ptr<BaseComponent>> mComponents;
    mComponents.emplace_back(new PositionComponent);
    auto *pos = mComponents[0]->as<PositionComponent>();
    pos->x = 1337;
    return 0;
}

在T*as()方法中,我应该使用static_cast还是dynamic_cast?转换会失败吗?我需要像这样dynamic_cast吗?

    auto *ptr = dynamic_cast<T*>(this);
    if(ptr == nullptr)
        throw std::runtime_error("D'oh!");
    return ptr;

在您的情况下,没有办法静态地判断this是否是正确的类型。你可能想要的是一个CRTP(奇怪的重复模板模式):

template <class T>
struct BaseComponent
{
    T* as()
    {
        return static_cast<T*>(this);
    }
    virtual ~BaseComponent() {}
};
template <typename T>
struct Component : public BaseComponent<T>
{
    virtual ~Component() {}
};
struct PositionComponent : public Component<PositionComponent>
{
    float x, y, z;
    virtual ~PositionComponent() {}
};

你可以这样做:

auto x = yourBaseComponent.as();

并且静态地具有正确的子类型。

您提供的代码是正确的,格式良好,但通常强制转换是不安全的。如果实际的对象不是PositionComponent,那么编译器会非常乐意地假设它是,并且您将导致未定义的行为。

如果用dynamic_cast替换强制转换,那么编译器将生成代码,在运行时验证转换是否有效。

真正的问题是你为什么需要这个。原因是有的,但通常情况下,使用强制转换表明您的设计存在问题。重新考虑是否可以做得更好(即重新设计代码,这样就不需要显式转换类型)

由于您使用的是unique_ptr<BaseComponent>,自然会有转换失败的时候:在向量中插入新数据和消耗该数据是在不相关的地方完成的,而且编译器无法强制执行。

以下是无效强制转换的示例:

struct AnotherComponent : public Component<AnotherComponent>
{
    virtual ~AnotherComponent () {}
};
std::vector<std::unique_ptr<BaseComponent>> mComponents;
mComponents.emplace_back(new AnotherComponent);
// !!! This code compiles, but it is fundamentally broken !!!
auto *pos = mComponents[0]->as<PositionComponent>();
pos->x = 1337;

在这方面,使用dynamic_cast将提供更好的保护,防止as<T>函数的错误使用。请注意,错误的用法可能不是故意的:任何时候编译器都无法为您检查类型,并且您可能存在类型不匹配的情况,您应该更喜欢dynamic_cast<T>

这里有一个小演示来说明dynamic_cast将如何为您提供一定程度的保护。

在转换从基类派生的多态对象时,应始终使用dynamic_cast

mComponents[0]不是PositionComponent(或从中派生的类)的情况下,上述代码将失败。由于让mComponents保存指向BaseComponent的指针的全部目的是为了将PositionComponent对象以外的其他对象放入向量中,所以我认为您需要注意特定的场景。

通常,当您使用dynamic_cast(或者通常铸造从公共基类派生的对象)时,这是一种"臭味"。通常情况下,这意味着对象不应该保存在一个公共容器中,因为它们之间的关系不够密切。