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&lt;Action&gt; 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::stackboost::shared_ptr&lt;Action&gt; 或类似的。或者您可以只使用原始指针,但您必须小心正确管理所有权。

【讨论】:

小心,你不应该将 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); 

当然你应该使用 newdelete 来为实际的 Action 对象创建和释放内存,我认为你不能使用auto_ptr 使用标准容器,因此您必须手动管理内存或实施其他方法。但是,如果您将 undo 缓冲区包装在一个类中,这应该不是什么大问题。

【讨论】:

或者 boost::shared_ptr ot std::tr1::shared_ptr 如果你想小心内存泄漏。有很多建议不要在 STL 容器中存储原始指针,除非你真的无法避免。 除了原始指针难以处理的标准建议之外,还有什么特别的理由推荐这种建议吗? 可能不会。难以处理意味着易于处理,这也节省了时间。 当然,据我所知,唯一的原因是难以管理生命周期。说“不要存储具有所有权的原始指针”可能比“不要存储原始指针”更好。没有所有权的指针很好:std::map&lt;char, const char *&gt; m; m['n'] = "north"; m['s'] = "south"; 等。

以上是关于C++ 撤消/重做实现中的抽象类问题的主要内容,如果未能解决你的问题,请参考以下文章

第52课 C++中的抽象类和接口

java中,一个类实现某个接口,必须重写接口中的所有方法吗?

C++纯虚函数和抽象类

C++面向对象:C++ 接口(抽象类)

弄清楚是啥让 VS2008 中的 C++ 类抽象

Java的接口和C++的虚类的相同和不同处?