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
相关产品推荐
相关产品推荐

