C++ 撤消/重做实现中的抽象类问题
Posted
技术标签:
【中文标题】C++ 撤消/重做实现中的抽象类问题【英文标题】:Abstract classes issue in C++ undo/redo implementation 【发布时间】:2010-01-12 00:52:12 【问题描述】:我已经定义了一个像这样的“Action”纯抽象类:
class Action
public:
virtual void execute () = 0;
virtual void revert () = 0;
virtual ~Action () = 0;
;
并用一个类表示用户可以执行的每个命令。
对于实际的撤消/重做,我想做这样的事情:
撤消
Action a = historyStack.pop();
a.revert();
undoneStack.push(a);
重做
Action a = undoneStack.pop();
a.execute();
historyStack.push(a);
编译器显然不接受这个,因为“Action”是一个不能被实例化的抽象类。
那么,我是否必须重新设计所有内容,还是有一个简单的解决方案来解决这个问题?
【问题讨论】:
【参考方案1】:您应该将动作存储为指针,这将使编译器满意。
std::vector<Action*> historyStack;
/*...*/
historyStack.push_back(new EditAction(/*...*/));
Action* a = historyStack.pop();
a->revert();
undoneStack.push(a);
std::vector<Action> historyStack;
不起作用还有另一个原因,那就是切片。当将派生类的对象添加到向量时,它们将被强制转换为基类并失去所有的多态性。更多信息在这里:What is object slicing?
编辑研究使用 ptr_vector 管理向量中对象的生命周期:http://www.boost.org/doc/libs/1_37_0/libs/ptr_container/doc/tutorial.html
【讨论】:
别忘了删除。智能指针会有所帮助,Boost Pointer Containers 等特定于指针的容器也会有所帮助。 好的,谢谢,这里肯定需要智能指针容器。 虽然有了这些功能,你想要stack
,而不是vector
。 :)
但是没有 boost::ptr_stack,即使可能,std::stack 在 ptr_vector 上也不会做正确的事情,因为我们需要能够弹出东西并收回所有权。 std::stack 的 pop() 操作与 ptr_vector 不同。
std::vector<:unique_ptr>> 将是存储指针的好方法......我们也可以通过这种方式将它们取出来获取所有权【参考方案2】:
无论如何,多态调度只能通过 C++ 中的指针或引用发生。您可能无法创建 Action 的值,但您会发现您将能够创建指向 Actions 的引用和指针。
pop 只需要返回一个(可能是智能的)指针或对 Action 的引用。一种方法可能是使用 std::auto_ptr 和 boost::ptr_deque,这将(正确使用)确保在之后适当地清理操作。
std::auto_ptr<Action> a = historyStack.pop_front();
a->revert();
undoneStack.push_front(a);
另一个选项可以是std::stack
或boost::shared_ptr<Action>
或类似的。或者您可以只使用原始指针,但您必须小心正确管理所有权。
【讨论】:
小心,你不应该将 auto_ptr 存储在 STL 容器中。当容器自动调整大小时,数据可能会丢失。请参阅devx.com/tips/Tip/13606 请注意,Logan 并不是在暗示这一点,但很容易犯这个错误。【参考方案3】:您应该将指向已执行操作的指针存储在队列中。
例如:
std::vector<Action*> historyStack;
std::vector<Action*> undoneStack;
然后:
Action* a = historyStack.pop_back();
a->revert();
undoneStack.push_back( a );
还有:
Action* a = undoneStack.pop_back();
a->execute();
historyStack.push_back(a);
当然你应该使用 new 和 delete 来为实际的 Action 对象创建和释放内存,我认为你不能使用auto_ptr 使用标准容器,因此您必须手动管理内存或实施其他方法。但是,如果您将 undo 缓冲区包装在一个类中,这应该不是什么大问题。
【讨论】:
或者 boost::shared_ptr ot std::tr1::shared_ptr 如果你想小心内存泄漏。有很多建议不要在 STL 容器中存储原始指针,除非你真的无法避免。 除了原始指针难以处理的标准建议之外,还有什么特别的理由推荐这种建议吗? 可能不会。难以处理意味着易于处理,这也节省了时间。 当然,据我所知,唯一的原因是难以管理生命周期。说“不要存储具有所有权的原始指针”可能比“不要存储原始指针”更好。没有所有权的指针很好:std::map<char, const char *> m; m['n'] = "north"; m['s'] = "south";
等。以上是关于C++ 撤消/重做实现中的抽象类问题的主要内容,如果未能解决你的问题,请参考以下文章