打电话给 C++自动基类方法

发布于 2024-09-07 00:16:46 字数 865 浏览 3 评论 0原文

我正在尝试实现命令设计模式,但我遇到了一个概念问题。假设您有一个基类和一些子类,如下例所示:

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 实例上调用运算符 () 时,我想将 *this 添加到堆栈中(主要用于撤消目的)通过调用命令的注册方法。我想避免从 SomeCommand::operator()() 调用“register”,但要自动调用它(以某种方式;-))

我知道当您构造一个子类(例如 SomeCommand)时,基类构造函数是自动调用,所以我可以在那里添加对“注册”的调用。在调用operator()()之前我不想调用register。

我该怎么做?我想我的设计有些缺陷,但我真的不知道如何实现这一点。

I'm trying to implement the command design pattern, but I'm stumbling accross a conceptual problem. Let's say you have a base class and a few subclasses like in the example below:

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
};

The thing is, everytime operator () is called on a SomeCommand instance, I'd like to add *this to a stack (mostly for undo purposes) by calling the Command's register method. I'd like to avoid calling "register" from SomeCommand::operator()(), but to have it called automaticaly (someway ;-) )

I know that when you construct a sub class such as SomeCommand, the base class constructor is called automaticaly, so I could add a call to "register" there. The thing I don't want to call register until operator()() is called.

How can I do this? I guess my design is somewhat flawed, but I don't really know how to make this work.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

桃酥萝莉 2024-09-14 00:16:46

看起来您似乎可以从 NVI(非虚拟接口)习惯中受益。 command 对象的接口将没有虚拟方法,但会调用私有扩展点:

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 的原始文章,他介绍了这个概念(尚未命名)

It looks as if you can benefit from the NVI (Non-Virtual Interface) idiom. There the interface of the command object would have no virtual methods, but would call into private extension points:

class command {
public:
   void operator()() {
      do_command();
      add_to_undo_stack(this);
   }
   void undo();
private:
   virtual void do_command();
   virtual void do_undo();
};

There are different advantages to this approach, first of which is that you can add common functionality in the base class. Other advantages are that the interface of your class and the interface of the extension points is not bound to each other, so you could offer different signatures in your public interface and the virtual extension interface. Search for NVI and you will get much more and better explanations.

Addendum: The original article by Herb Sutter where he introduces the concept (yet unnamed)

黎歌 2024-09-14 00:16:46

将运算符拆分为两种不同的方法,例如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();
   };

Split the operator in two different methods, e.g. execute and executeImpl (to be honest, I don't really like the () operator). Make Command::execute non-virtual, and Command::executeImpl pure virtual, then let Command::execute perform the registration, then call it executeImpl, like this:

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

class SomeCommand
   {
   protected:
      virtual ResultType executeImpl();
   };
青衫负雪 2024-09-14 00:16:46

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

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

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

Assuming it's a 'normal' application with undo and redo, I wouldn't try and mix managing the stack with the actions performed by the elements on the stack. It will get very complicated if you either have multiple undo chains (e.g. more than one tab open), or when you do-undo-redo, where the command has to know whether to add itself to undo or move itself from redo to undo, or move itself from undo to redo. It also means you need to mock the undo/redo stack to test the commands.

If you do want to mix them, then you will have three template methods, each taking the two stacks (or the command object needs to have references to the stacks it operates on when created), and each performing the move or add, then calling the function. But if you do have those three methods, you will see that they don't actually do anything other than call public functions on the command and are not used by any other part of the command, so become candidates the next time you refactor your code for cohesion.

Instead, I'd create an UndoRedoStack class which has an execute_command(Command*command) function, and leave the command as simple as possible.

橪书 2024-09-14 00:16:46

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

有关为什么需要 NVI 的详细讨论,请参阅 Herb Sutter 的 C++ 编码标准。他甚至建议将所有公共函数设为非虚拟,以实现可重写代码与公共接口代码的严格分离(公共接口代码不应该是可重写的,以便您始终可以进行一些集中控制并添加工具,前/后)状况检查,以及您需要的其他任何内容)。

class Command 
{
public:
   void operator()() 
   {
      do_command();
      add_to_undo_stack(this);
   }

   void undo()
   {
      // This might seem pointless now to just call do_undo but 
      // it could become beneficial later if you want to do some
      // error-checking, for instance, without having to do it
      // in every single command subclass's undo implementation.
      do_undo();
   }

private:
   virtual void do_command() = 0;
   virtual void do_undo() = 0;
};

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

尽管如此,学习 NVI 应该还是很有帮助的。我见过太多开发人员出于历史优势而编写这样的纯虚拟接口,当只需要在一个中心位置实现时,他们只需将相同的代码添加到定义它的每个子类中。它是一个非常方便的工具,可以添加到您的编程工具箱中。

Basically Patrick's suggestion is the same as David's which is also the same as mine. Use NVI (non-virtual interface idiom) for this purpose. Pure virtual interfaces lack any kind of centralized control. You could alternatively create a separate abstract base class that all commands inherit, but why bother?

For detailed discussion about why NVIs are desirable, see C++ Coding Standards by Herb Sutter. There he goes so far as to suggest making all public functions non-virtual to achieve a strict separation of overridable code from public interface code (which should not be overridable so that you can always have some centralized control and add instrumentation, pre/post-condition checking, and whatever else you need).

class Command 
{
public:
   void operator()() 
   {
      do_command();
      add_to_undo_stack(this);
   }

   void undo()
   {
      // This might seem pointless now to just call do_undo but 
      // it could become beneficial later if you want to do some
      // error-checking, for instance, without having to do it
      // in every single command subclass's undo implementation.
      do_undo();
   }

private:
   virtual void do_command() = 0;
   virtual void do_undo() = 0;
};

If we take a step back and look at the general problem instead of the immediate question being asked, I think Pete offers some very good advice. Making Command responsible for adding itself to the undo stack is not particularly flexible. It can be independent of the container in which it resides. Those higher-level responsibilities should probably be a part of the actual container which you can also make responsible for executing and undoing the command.

Nevertheless, it should be very helpful to study NVI. I've seen too many developers write pure virtual interfaces like this out of the historical benefits they had only to add the same code to every subclass that defines it when it need only be implemented in one central place. It is a very handy tool to add to your programming toolbox.

饮湿 2024-09-14 00:16:46

我曾经有一个创建 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);
};

曾经有一个名为OperationManager 的类。这注册了不同的函数并在其中创建了它们的实例,如下所示:

OperationManager& opMgr = OperationManager::GetInstance();
opMgr.register<MoveOperation>();

注册函数就像:

template <typename T>
void OperationManager::register()
{
    T* op = new T();
    const std::string& op_name = op->getName();
    if(mOperations.count(op_name))
    {
        delete op;
    }else{
        mOperations[op_name] = op;
    }
}

每当要执行函数时,它将基于当前选定的对象或它需要处理的任何内容。注意:就我而言,我不需要发送每个对象应移动多少的详细信息,因为一旦将其设置为活动函数,MoveOperation 就会从输入设备计算该详细信息。
在OperationManager中,执行一个函数就像:

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的undo函数如下所示:

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不知道每个函数需要什么参数,因此很容易管理不同的函数。

I once had a project to create a 3D modelling application and for that I used to have the same requirement. As far as I understood when working on it was that no matter what and operation should always know what it did and therefore should know how to undo it. So I had a base class created for each operation and it's operation state as shown below.

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;
};

Creating a function and it's state would be like:

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);
};

There used to be a class called OperationManager. This registered different functions and created instances of them within it like:

OperationManager& opMgr = OperationManager::GetInstance();
opMgr.register<MoveOperation>();

The register function was like:

template <typename T>
void OperationManager::register()
{
    T* op = new T();
    const std::string& op_name = op->getName();
    if(mOperations.count(op_name))
    {
        delete op;
    }else{
        mOperations[op_name] = op;
    }
}

Whenever a function was to be executed, it would be based on the currently selected objects or the whatever it needs to work on. NOTE: In my case, I didn't need to send the details of how much each object should move because that was being calculated by MoveOperation from the input device once it was set as the active function.
In the OperationManager, executing a function would be like:

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);
        }
    }
}

When there's a necessity to undo, you do that from the OperationManager like:
OperationManager::GetInstance().undo();
And the undo function of the OperationManager looks like this:

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.
        }
    }
}

This made the OperationManager not be aware of what arguments each function needs and so was easy to manage different functions.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文