C++ 调用C++;自动基类方法

C++ 调用C++;自动基类方法,c++,class,methods,base-class,C++,Class,Methods,Base Class,我正试图实现,但我遇到了一个概念上的问题。假设您有一个基类和几个子类,如下例所示: class Command : public boost::noncopyable { virtual ResultType operator()()=0; //Restores the model state as it was before command's execution. virtual void undo()=0; //Registers this comman

我正试图实现,但我遇到了一个概念上的问题。假设您有一个基类和几个子类,如下例所示:

class Command : public boost::noncopyable {
    virtual ResultType operator()()=0;

    //Restores the model state as it was before command's execution.
    virtual void undo()=0;

    //Registers this command on the command stack.
    void register();
};


class SomeCommand : public Command {
    virtual ResultType operator()(); // Implementation doesn't really matter here
    virtual void undo(); // Same
};
问题是,每次在SomeCommand实例上调用操作符
()
,我都想通过调用命令的register方法将*这添加到堆栈中(主要用于撤销目的)。我不想从SomeCommand::operator()()调用“register”,而是想让它自动调用(以某种方式;-)

我知道,当您构造一个子类(如SomeCommand)时,基类构造函数称为automaticaly,因此我可以在那里添加对“register”的调用。在调用操作符()之前,我不想调用register


我该怎么做?我想我的设计有点缺陷,但我真的不知道如何使它工作。

看起来你可以从NVI(非虚拟接口)习惯用法中获益。在那里,
命令
对象的接口将没有虚拟方法,但将调用私有扩展点:

class command {
public:
   void operator()() {
      do_command();
      add_to_undo_stack(this);
   }
   void undo();
private:
   virtual void do_command();
   virtual void do_undo();
};
这种方法有不同的优点,首先是可以在基类中添加公共功能。其他优点是类的接口和扩展点的接口彼此不绑定,因此可以在公共接口和虚拟扩展接口中提供不同的签名。搜索NVI,你会得到更多更好的解释


附录:Herb Sutter在原著中介绍了这个概念(尚未命名)

将操作符拆分为两种不同的方法,例如execute和executeImpl(老实说,我不太喜欢()操作符)。使Command::execute非虚拟,Command::executeImpl纯虚拟,然后让Command::execute执行注册,然后将其称为executeImpl,如下所示:

class Command
   {
   public:
      ResultType execute()
         {
         ... // do registration
         return executeImpl();
         }
   protected:
      virtual ResultType executeImpl() = 0;
   };

class SomeCommand
   {
   protected:
      virtual ResultType executeImpl();
   };
void OperationManager::undo()
{
    if(!mUndoStack.empty())
    {
        OperationState* state = mUndoStack.pop();
        if(state->getParent().undo(state))
        {
            mRedoStack.push(state);
        }else{
            // Throw an exception or warn the user.
        }
    }
}

假设它是一个具有撤销和重做功能的“普通”应用程序,我不会尝试将管理堆栈与堆栈上的元素执行的操作混为一谈。如果您有多个撤消链(例如,打开了多个选项卡),或者在执行撤消重做时,命令必须知道是将自身添加到撤消还是将自身从重做移动到撤消,或者将自身从撤消移动到重做,这将变得非常复杂。这还意味着您需要模拟undo/redo堆栈来测试命令

如果您确实想混合使用它们,那么您将有三个模板方法,每个模板方法取两个堆栈(或者命令对象需要在创建时对其操作的堆栈进行引用),每个模板方法执行移动或添加,然后调用函数。但是如果你有这三种方法,你会发现它们实际上除了在命令上调用公共函数之外什么都不做,并且不被命令的任何其他部分使用,所以在下次重构代码以实现内聚性时,你可以成为候选方法


相反,我会创建一个UndoRedoStack类,它有一个execute_command(command*command)函数,并让命令尽可能简单。

基本上Patrick的建议与David的相同,David的建议也与我的相同。为此,请使用NVI(非虚拟接口习惯用法)。纯虚拟接口缺乏任何类型的集中控制。您也可以创建一个单独的抽象基类,由所有命令继承,但为什么要麻烦呢

详细讨论为什么NVIs是可取的,见萨特的C++编码标准。在这里,他甚至建议将所有公共函数设置为非虚拟函数,以实现可重写代码与公共接口代码的严格分离(公共接口代码不应被重写,这样您就可以始终拥有一些集中控制,并添加插装、预/后条件检查,以及您需要的任何其他内容)

如果我们退后一步,看看一般的问题,而不是眼前的问题,我认为皮特提供了一些非常好的建议。让命令负责将其自身添加到撤消堆栈并不是特别灵活。它可以独立于它所在的容器。这些更高级别的职责可能是实际容器的一部分,您还可以让它负责执行和撤消命令


然而,研究NVI应该是非常有帮助的。我见过太多的开发人员编写这样的纯虚拟接口,这是出于历史上的好处,他们只需在定义它的每个子类中添加相同的代码,而这些子类只需要在一个中心位置实现。这是一个非常方便的工具,可以添加到您的编程工具箱中。

我曾经有一个创建3D建模应用程序的项目,对此我也有同样的要求。据我所知,在处理它时,无论发生什么,操作都应该知道它做了什么,因此应该知道如何撤销它。所以我为每个操作创建了一个基类,它的操作状态如下所示

class OperationState
{
protected:
    Operation& mParent;
    OperationState(Operation& parent);
public:
    virtual ~OperationState();
    Operation& getParent();
};

class Operation
{
private:
    const std::string mName;
public:
    Operation(const std::string& name);
    virtual ~Operation();

    const std::string& getName() const{return mName;}

    virtual OperationState* operator ()() = 0;

    virtual bool undo(OperationState* state) = 0;
    virtual bool redo(OperationState* state) = 0;
};
创建函数及其状态如下:

class MoveState : public OperationState
{
public:
    struct ObjectPos
    {
        Object* object;
        Vector3 prevPosition;
    };
    MoveState(MoveOperation& parent):OperationState(parent){}
    typedef std::list<ObjectPos> PrevPositions;
    PrevPositions prevPositions;
};

class MoveOperation : public Operation
{
public:
    MoveOperation():Operation("Move"){}
    ~MoveOperation();

    // Implement the function and return the previous
    // previous states of the objects this function
    // changed.
    virtual OperationState* operator ()();

    // Implement the undo function
    virtual bool undo(OperationState* state);
    // Implement the redo function
    virtual bool redo(OperationState* state);
};
void OperationManager::execute(const std::string& operation_name)
{
    if(mOperations.count(operation_name))
    {
        Operation& op = *mOperations[operation_name];
        OperationState* opState = op();
        if(opState)
        {
            mUndoStack.push(opState);
        }
    }
}
当需要撤消时,可以从OperationManager执行此操作,如:
OperationManager::GetInstance().undo()
OperationManager的撤消功能如下所示:

class Command
   {
   public:
      ResultType execute()
         {
         ... // do registration
         return executeImpl();
         }
   protected:
      virtual ResultType executeImpl() = 0;
   };

class SomeCommand
   {
   protected:
      virtual ResultType executeImpl();
   };
void OperationManager::undo()
{
    if(!mUndoStack.empty())
    {
        OperationState* state = mUndoStack.pop();
        if(state->getParent().undo(state))
        {
            mRedoStack.push(state);
        }else{
            // Throw an exception or warn the user.
        }
    }
}

这使得OperationManager不知道每个函数需要哪些参数,因此很容易管理不同的函数。

命令的成员应该是公共的吗?
谢谢您的帮助。是的,他们应该是公开的,我忘了把它写进密码里。每次调用SomeCommand实例运算符()时,我都希望将其添加到堆栈中。您可以将其视为一种“撤消堆栈”。从构造SomeCommand对象到调用运算符()之间可能会出现一些延迟。因此,我无法在构建时将其添加到堆栈中,因为这可能会导致程序尝试撤消尚未完成的操作。register是一个关键字,您无法命名方法register。另请参阅。Nice
void OperationManager::undo()
{
    if(!mUndoStack.empty())
    {
        OperationState* state = mUndoStack.pop();
        if(state->getParent().undo(state))
        {
            mRedoStack.push(state);
        }else{
            // Throw an exception or warn the user.
        }
    }
}