4

假设我们有以下代码:

#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?有时转换会失败吗?我需要像这样动态转换吗?

    auto *ptr = dynamic_cast<T*>(this);

    if(ptr == nullptr)
        throw std::runtime_error("D'oh!");

    return ptr;
4

4 回答 4

3

在您的情况下,无法静态判断this类型是否正确。您可能想要的是一个 CRTP(Curiously recurring template pattern):

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();

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

于 2013-08-12T20:26:41.983 回答
2

您提供的代码是正确且格式正确的,但通常演员表并不安全。如果实际对象不是 a PositionComponent,那么编译器会很乐意假设它是,并且您将导致未定义的行为。

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

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

于 2013-08-12T20:21:42.073 回答
1

由于您使用的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为您提供一定程度的保护。

于 2013-08-12T20:21:25.193 回答
1

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

mComponents[0]不是PositionComponent(或从其派生的类)的情况下,上述代码将失败。由于mComponents持有一个指针的全部目的BaseComponent是为了让您可以将PositionComponent对象以外的其他东西放入向量中,我想说您需要注意这种特定情况。

通常,当您使用dynamic_cast(或通常转换从公共基类派生的对象)时,这是一种“难闻的气味”。通常这意味着对象不应该放在一个公共容器中,因为它们之间的关系不够密切。

于 2013-08-12T20:21:29.447 回答