You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++中如何在循环内获取特定类型的结构体?

问题描述

我定义了以下结构体用于动画系统:

struct IAnimateAction{};
struct AnimateActions {
    int mSec;
    std::vector<IAnimateAction> actions;
};

struct ZoomAction : public IAnimateAction {
    explicit ZoomAction(int targetZoom);
    int targetZoom;
};
struct TranslateAction : public IAnimateAction{
    TranslateAction(int targetX, int targetY);
    int targetX;
    int targetY;
};
struct TurnAction: public IAnimateAction {
    explicit TurnAction(float targetAngle);
    float targetAngle; //radials
};

可以将不同动作组合到AnimateActions中,示例如下:

AnimateActions actions;
actions.mSec = 5000;
actions.actions.push_back(ZoomAction(130));
actions.actions.push_back(TurnAction(270));
c.animatelist.push(actions);

这些动作会被存储在std::queue<AnimateActions> animatelist;中,表示动画系统要在指定时间内同时执行多个动作。但在遍历动作时遇到了问题:无法判断当前获取的是哪种派生结构体类型,希望在以下循环中IDE能给出对应特定结构体的类型提示:

if (!animatelist.empty()) {
    auto element = animatelist.front();
    for (auto action : element.actions) {
        // 如何转换为特定结构体?
    }
    animatelist.pop();
}
首先修正对象切片问题

你当前的std::vector<IAnimateAction>会触发对象切片:当把派生类对象存入基类容器时,派生类特有的成员(比如ZoomAction::targetZoom)会被截断,只保留基类部分的数据,后续无论怎么转换都无法恢复这些丢失的成员。

必须先修正这个问题,有两种常用方案:


方案1:使用多态(虚函数)

给基类添加虚函数,让派生类实现具体逻辑,这样不需要显式判断类型,IDE也能识别派生类的类型:

步骤1:修改基类和派生类

#include <memory>

struct IAnimateAction {
    // 虚析构函数,确保派生类对象能正确销毁
    virtual ~IAnimateAction() = default;
    // 定义虚函数,派生类实现具体动作逻辑
    virtual void execute() = 0;
};

struct ZoomAction : public IAnimateAction {
    explicit ZoomAction(int targetZoom) : targetZoom(targetZoom) {}
    int targetZoom;

    void execute() override {
        // 执行缩放逻辑,比如:
        // camera.setZoom(targetZoom);
    }
};

struct TranslateAction : public IAnimateAction{
    TranslateAction(int targetX, int targetY) : targetX(targetX), targetY(targetY) {}
    int targetX;
    int targetY;

    void execute() override {
        // 执行平移逻辑
        // camera.setPosition(targetX, targetY);
    }
};

struct TurnAction: public IAnimateAction {
    explicit TurnAction(float targetAngle) : targetAngle(targetAngle) {}
    float targetAngle; //radials

    void execute() override {
        // 执行旋转逻辑
        // camera.setRotation(targetAngle);
    }
};

步骤2:修改容器类型

使用智能指针存储基类对象,避免切片:

struct AnimateActions {
    int mSec;
    std::vector<std::unique_ptr<IAnimateAction>> actions;
};

// 添加动作的方式
AnimateActions actions;
actions.mSec = 5000;
actions.actions.push_back(std::make_unique<ZoomAction>(130));
actions.actions.push_back(std::make_unique<TurnAction>(270));
c.animatelist.push(actions);

步骤3:遍历执行动作

此时遍历不需要显式判断类型,直接调用虚函数即可,IDE会根据实际类型识别:

if (!animatelist.empty()) {
    auto element = animatelist.front();
    for (auto& action : element.actions) {
        action->execute(); // IDE会识别当前action指向的派生类类型
    }
    animatelist.pop();
}

如果需要在某些场景下判断具体类型,可以使用dynamic_cast:

if (!animatelist.empty()) {
    auto element = animatelist.front();
    for (auto& action : element.actions) {
        if (auto* zoomAction = dynamic_cast<ZoomAction*>(action.get())) {
            // 这里IDE会提示ZoomAction的成员,比如zoomAction->targetZoom
            zoomAction->targetZoom;
        } else if (auto* turnAction = dynamic_cast<TurnAction*>(action.get())) {
            // 访问turnAction->targetAngle
        }
    }
    animatelist.pop();
}

方案2:使用std::variant(C++17及以上)

如果不需要多态逻辑,std::variant是更轻量的选择,它可以直接存储不同类型的对象,且IDE能完美识别类型:

步骤1:定义variant类型

#include <variant>

// 先去掉基类,直接定义动作类型
struct ZoomAction {
    explicit ZoomAction(int targetZoom) : targetZoom(targetZoom) {}
    int targetZoom;
};
struct TranslateAction {
    TranslateAction(int targetX, int targetY) : targetX(targetX), targetY(targetY) {}
    int targetX;
    int targetY;
};
struct TurnAction {
    explicit TurnAction(float targetAngle) : targetAngle(targetAngle) {}
    float targetAngle; //radials
};

// 定义variant类型,包含所有动作类型
using AnimateAction = std::variant<ZoomAction, TranslateAction, TurnAction>;

struct AnimateActions {
    int mSec;
    std::vector<AnimateAction> actions;
};

步骤2:添加动作

AnimateActions actions;
actions.mSec = 5000;
actions.actions.push_back(ZoomAction(130));
actions.actions.push_back(TurnAction(270));
c.animatelist.push(actions);

步骤3:遍历并处理动作

使用std::visit来处理不同类型,IDE会在lambda中给出对应类型的提示:

#include <visit>

if (!animatelist.empty()) {
    auto element = animatelist.front();
    for (auto& action : element.actions) {
        std::visit([](auto&& act) {
            using T = std::decay_t<decltype(act)>;
            if constexpr (std::is_same_v<T, ZoomAction>) {
                // IDE会提示act.targetZoom
                act.targetZoom;
            } else if constexpr (std::is_same_v<T, TurnAction>) {
                // IDE会提示act.targetAngle
                act.targetAngle;
            } else if constexpr (std::is_same_v<T, TranslateAction>) {
                // IDE会提示act.targetX、act.targetY
                act.targetX;
            }
        }, action);
    }
    animatelist.pop();
}

或者直接为每个类型编写处理逻辑:

std::visit(overloaded{
    [](ZoomAction& act) { /* 处理缩放 */ },
    [](TranslateAction& act) { /* 处理平移 */ },
    [](TurnAction& act) { /* 处理旋转 */ }
}, action);

其中overloaded需要自己定义(C20可直接用std::visit的重载版本,C17需要手动实现):

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

内容的提问来源于stack exchange,提问作者Oli

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 20:27:30