方法指针和继承 // 一种策略模式 (C++)

发布于 2024-11-28 02:59:03 字数 3385 浏览 1 评论 0原文

在我的设计中,有一个类从文件中读取信息。读取的信息代表一个作业(为了简单起见,它是一个整数,即“作业 ID”)。文件读取器类可以接受可以处理此类作业的对象。现在我的想法是,创建一个接口,例如“IJobHandler”,它有一个纯虚函数“DoJob()”,然后你可以调用类似这样的东西,

FileReader fr;
Class1 c1; // has a base class IAcceptor with virtual method HandleJobId()
Class2 c2; // has a base class IAcceptor with virtual method HandleJobId()
fr.Register(c1);
fr.Register(c2);
fr.doJob(1); // calls c1.HandleJobId()
fr.doJob(2); // class c2.HandleJobId()

这样就可以正常工作。但是,如果某个类可以处理两个或多个作业 ID,会发生什么情况?但该类只能实现一种方法(HandleJobId())。下面的内容不是很好吗: fr.Register(c1, c1::Handle_1()) 或类似的东西?

也许我现在的意图还不是很清楚。但您将在下面更大的代码示例中看到它。抱歉,代码块很大,但我不知道如何准确地解释它......

class IAcceptable
{
public:
    // interface; implementors should return map of job-ids (int)
    // and a kind of pointer to a method which should be called to
    // handle the job.
    virtual std::map<int, SOME_KIND_OF_FUNCTION_POINTER> GetJobIds() const = 0;
};

class Class12 : public IAcceptable
{
public:
    void Handle_1(){} // method to handle job id 1
    void Handle_2(){} // method to handle job id 2

    virtual std::map<int, SOME_KIND_OF_FUNCTION_POINTER> GetJobIds() const
    {
        std::map<int, SOME_KIND_OF_FUNCTION_POINTER> intToMethodMap;
        // return map, which says: "I can handle job id 1, by calling Handle_1(), so I give you c12 pointer to this method"
        // (same thing for job id 2 and Handle_2())
        intToMethodMap.insert(std::pair<int, SOME_KIND_OF_FUNCTION_POINTER>(1, POINTER_TO_Handle_1);
        intToMethodMap.insert(std::pair<int, SOME_KIND_OF_FUNCTION_POINTER>(2, POINTER_TO_Handle_2);
        return intToMethodMap;
    }
};

class Class34 : public IAcceptable
{
    void Handle_3(){} // method to handle job id 3
    void Handle_4(){} // method to handle job id 4
    virtual std::map<int, SOME_KIND_OF_FUNCTION_POINTER> GetJobIds() const
    {
        std::map<int, SOME_KIND_OF_FUNCTION_POINTER> intToMethodMap;
        // return map, which says: "I can handle job id 3, by calling Handle_3(), so I give you c12 pointer to this method"
        // (same thing for job id 4 and Handle_4())
        intToMethodMap.insert(std::pair<int, SOME_KIND_OF_FUNCTION_POINTER>(3, POINTER_TO_Handle_3);
        intToMethodMap.insert(std::pair<int, SOME_KIND_OF_FUNCTION_POINTER>(4, POINTER_TO_Handle_4);
        return intToMethodMap;
    }
};

class FileReader
{
public:
    // register an IAcceptable
    // and add its handlers to the local list
    void Register(const IAcceptable& acc)
    {
        m_handlers.insert(acc.GetJobIds());
    }

    // if some job is to do, search for the job id and call 
    // the found function
    void doSomeJob(int i)
    {
        std::map<int, SOMEFUNCTION>::iterator specificHandler = m_handlers.find(i);
        // call here (specificHandler->second)()
    }
private:
    std::map<int, SOMEFUNCTION> m_handlers;
};


int main()
{
    Class12 c12;   // can handle job id 1 and 2
    Class34 c34;   // can handle job id 3 and 4

    FileReader fr;
    fr.Register(c12);
    fr.Register(c34);

    fr.doSomeJob(1);  // should lead to this call: c12->Handle_1()
    fr.doSomeJob(2);  // c12->Handle_2();
    fr.doSomeJob(3);  // c34->Handle_3();
    fr.doSomeJob(4);  // c34->Handle_4();
}

好吧,也许设计是我的问题,有人可以给我一个提示如何让它变得更好:)

In my design, there is a class which reads information from file. The read info represents a job (for simplicity, it's an integer, which is "job id"). The file reader class can accept objects which can handle such a job. Now my idea was, to make an Interface, e.g. "IJobHandler" which has a pure virtual function "DoJob()" and then you can call something like

FileReader fr;
Class1 c1; // has a base class IAcceptor with virtual method HandleJobId()
Class2 c2; // has a base class IAcceptor with virtual method HandleJobId()
fr.Register(c1);
fr.Register(c2);
fr.doJob(1); // calls c1.HandleJobId()
fr.doJob(2); // class c2.HandleJobId()

This would work fine. But what happens, if some class can handle two or more job ids? But there is only one method which this class can implement (HandleJobId()). Wouldn't the following be nice:
fr.Register(c1, c1::Handle_1()) or something like that?

Maybe my intention is not very clear right now. But you will se it on the bigger code example below. Sorry for the big code block, but I don't know how to explain it that exactly...

class IAcceptable
{
public:
    // interface; implementors should return map of job-ids (int)
    // and a kind of pointer to a method which should be called to
    // handle the job.
    virtual std::map<int, SOME_KIND_OF_FUNCTION_POINTER> GetJobIds() const = 0;
};

class Class12 : public IAcceptable
{
public:
    void Handle_1(){} // method to handle job id 1
    void Handle_2(){} // method to handle job id 2

    virtual std::map<int, SOME_KIND_OF_FUNCTION_POINTER> GetJobIds() const
    {
        std::map<int, SOME_KIND_OF_FUNCTION_POINTER> intToMethodMap;
        // return map, which says: "I can handle job id 1, by calling Handle_1(), so I give you c12 pointer to this method"
        // (same thing for job id 2 and Handle_2())
        intToMethodMap.insert(std::pair<int, SOME_KIND_OF_FUNCTION_POINTER>(1, POINTER_TO_Handle_1);
        intToMethodMap.insert(std::pair<int, SOME_KIND_OF_FUNCTION_POINTER>(2, POINTER_TO_Handle_2);
        return intToMethodMap;
    }
};

class Class34 : public IAcceptable
{
    void Handle_3(){} // method to handle job id 3
    void Handle_4(){} // method to handle job id 4
    virtual std::map<int, SOME_KIND_OF_FUNCTION_POINTER> GetJobIds() const
    {
        std::map<int, SOME_KIND_OF_FUNCTION_POINTER> intToMethodMap;
        // return map, which says: "I can handle job id 3, by calling Handle_3(), so I give you c12 pointer to this method"
        // (same thing for job id 4 and Handle_4())
        intToMethodMap.insert(std::pair<int, SOME_KIND_OF_FUNCTION_POINTER>(3, POINTER_TO_Handle_3);
        intToMethodMap.insert(std::pair<int, SOME_KIND_OF_FUNCTION_POINTER>(4, POINTER_TO_Handle_4);
        return intToMethodMap;
    }
};

class FileReader
{
public:
    // register an IAcceptable
    // and add its handlers to the local list
    void Register(const IAcceptable& acc)
    {
        m_handlers.insert(acc.GetJobIds());
    }

    // if some job is to do, search for the job id and call 
    // the found function
    void doSomeJob(int i)
    {
        std::map<int, SOMEFUNCTION>::iterator specificHandler = m_handlers.find(i);
        // call here (specificHandler->second)()
    }
private:
    std::map<int, SOMEFUNCTION> m_handlers;
};


int main()
{
    Class12 c12;   // can handle job id 1 and 2
    Class34 c34;   // can handle job id 3 and 4

    FileReader fr;
    fr.Register(c12);
    fr.Register(c34);

    fr.doSomeJob(1);  // should lead to this call: c12->Handle_1()
    fr.doSomeJob(2);  // c12->Handle_2();
    fr.doSomeJob(3);  // c34->Handle_3();
    fr.doSomeJob(4);  // c34->Handle_4();
}

Well, maybe the design is my problem and someone can give me a hint how to make it better :)

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

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

发布评论

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

评论(5

表情可笑 2024-12-05 02:59:03

这是一个完整的示例:

class IAcceptable; 

class DelegateBase
{
public: 
    virtual void Call() = 0; 
};

template <class Class> class Delegate: public DelegateBase
{
public: 
    typedef void (Class::*Function)(); 
    Delegate(Class* object, Function f): func(f) {}
    virtual void Call() { (object->*func)(); }

private: 
    Class* object; 
    Function func; 
}; 



class IAcceptable
{
public:
    // interface; implementors should return map of job-ids (int)
    // and a kind of pointer to a method which should be called to
    // handle the job.
    virtual std::map<int, DelegateBase*> GetJobIds() = 0;
};

class Class12 : public IAcceptable
{
public:
    void Handle_1(){} // method to handle job id 1
    void Handle_2(){} // method to handle job id 2

    virtual std::map<int, DelegateBase*> GetJobIds()
    {
        std::map<int, DelegateBase*> intToMethodMap;
        // return map, which says: "I can handle job id 1, by calling Handle_1(), so I give you c12 pointer to this method"
        // (same thing for job id 2 and Handle_2())
        intToMethodMap.insert(std::pair<int, DelegateBase*>(1, new Delegate<Class12>(this, &Class12::Handle_1)));
        intToMethodMap.insert(std::pair<int, DelegateBase*>(2, new Delegate<Class12>(this, &Class12::Handle_2)));
        return intToMethodMap;
    }
};

class Class34 : public IAcceptable
{
    void Handle_3(){} // method to handle job id 3
    void Handle_4(){} // method to handle job id 4
    virtual std::map<int, DelegateBase*> GetJobIds()
    {
        std::map<int, DelegateBase*> intToMethodMap;
        // return map, which says: "I can handle job id 3, by calling Handle_3(), so I give you c12 pointer to this method"
        // (same thing for job id 4 and Handle_4())
        intToMethodMap.insert(std::pair<int, DelegateBase*>(3, new Delegate<Class34>(this, &Class34::Handle_3)));
        intToMethodMap.insert(std::pair<int, DelegateBase*>(4, new Delegate<Class34>(this, &Class34::Handle_4)));
        return intToMethodMap;
    }
};

class FileReader
{
public:
    // register an IAcceptable
    // and add its handlers to the local list
    void Register(IAcceptable& acc)
    {
        std::map<int, DelegateBase*> jobIds = acc.GetJobIds(); 
        m_handlers.insert(jobIds.begin(), jobIds.end());
    }

    // if some job is to do, search for the job id and call 
    // the found function
    void doSomeJob(int i)
    {
        std::map<int, DelegateBase*>::iterator specificHandler = m_handlers.find(i);
        specificHandler->second->Call(); 
    }
private:
    std::map<int, DelegateBase*> m_handlers;
};


int _tmain(int argc, _TCHAR* argv[])
{
    Class12 c12;   // can handle job id 1 and 2
    Class34 c34;   // can handle job id 3 and 4

    FileReader fr;
    fr.Register(c12);
    fr.Register(c34);

    fr.doSomeJob(1);  // should lead to this call: c12->Handle_1()
    fr.doSomeJob(2);  // c12->Handle_2();
    fr.doSomeJob(3);  // c34->Handle_3();
    fr.doSomeJob(4);  // c34->Handle_4();

    return 0;
}
  1. 要调用成员函数,我们需要一个对象;所以你的映射不应该包含简单的方法指针,而应该包含可以封装完整调用的东西:一个对象+一个方法指针。那东西就是这里的代表。

  2. 为了确保方法被正确调用,即使它是在子类中定义的,我们需要正确地存储派生对象和方法指针类型(无转换)。因此我们将Delegate作为模板,以派生类作为其参数。

  3. 这意味着基于不同子类方法的委托是不兼容的,并且不能放入映射中。为了解决这个问题,我们引入了一个公共基类 DelegateBase 和虚函数 Call()。可以在不知道存储对象/方法的确切类型的情况下调用 Call(),并且它将被分派到类型正确的实现。现在我们可以在映射中存储 DelegateBase* 指针。

另请查看 boost::function 和 boost::bind,它们提供了上述内容的概括,我认为它们也可以用于您的目的。

Here's a complete example:

class IAcceptable; 

class DelegateBase
{
public: 
    virtual void Call() = 0; 
};

template <class Class> class Delegate: public DelegateBase
{
public: 
    typedef void (Class::*Function)(); 
    Delegate(Class* object, Function f): func(f) {}
    virtual void Call() { (object->*func)(); }

private: 
    Class* object; 
    Function func; 
}; 



class IAcceptable
{
public:
    // interface; implementors should return map of job-ids (int)
    // and a kind of pointer to a method which should be called to
    // handle the job.
    virtual std::map<int, DelegateBase*> GetJobIds() = 0;
};

class Class12 : public IAcceptable
{
public:
    void Handle_1(){} // method to handle job id 1
    void Handle_2(){} // method to handle job id 2

    virtual std::map<int, DelegateBase*> GetJobIds()
    {
        std::map<int, DelegateBase*> intToMethodMap;
        // return map, which says: "I can handle job id 1, by calling Handle_1(), so I give you c12 pointer to this method"
        // (same thing for job id 2 and Handle_2())
        intToMethodMap.insert(std::pair<int, DelegateBase*>(1, new Delegate<Class12>(this, &Class12::Handle_1)));
        intToMethodMap.insert(std::pair<int, DelegateBase*>(2, new Delegate<Class12>(this, &Class12::Handle_2)));
        return intToMethodMap;
    }
};

class Class34 : public IAcceptable
{
    void Handle_3(){} // method to handle job id 3
    void Handle_4(){} // method to handle job id 4
    virtual std::map<int, DelegateBase*> GetJobIds()
    {
        std::map<int, DelegateBase*> intToMethodMap;
        // return map, which says: "I can handle job id 3, by calling Handle_3(), so I give you c12 pointer to this method"
        // (same thing for job id 4 and Handle_4())
        intToMethodMap.insert(std::pair<int, DelegateBase*>(3, new Delegate<Class34>(this, &Class34::Handle_3)));
        intToMethodMap.insert(std::pair<int, DelegateBase*>(4, new Delegate<Class34>(this, &Class34::Handle_4)));
        return intToMethodMap;
    }
};

class FileReader
{
public:
    // register an IAcceptable
    // and add its handlers to the local list
    void Register(IAcceptable& acc)
    {
        std::map<int, DelegateBase*> jobIds = acc.GetJobIds(); 
        m_handlers.insert(jobIds.begin(), jobIds.end());
    }

    // if some job is to do, search for the job id and call 
    // the found function
    void doSomeJob(int i)
    {
        std::map<int, DelegateBase*>::iterator specificHandler = m_handlers.find(i);
        specificHandler->second->Call(); 
    }
private:
    std::map<int, DelegateBase*> m_handlers;
};


int _tmain(int argc, _TCHAR* argv[])
{
    Class12 c12;   // can handle job id 1 and 2
    Class34 c34;   // can handle job id 3 and 4

    FileReader fr;
    fr.Register(c12);
    fr.Register(c34);

    fr.doSomeJob(1);  // should lead to this call: c12->Handle_1()
    fr.doSomeJob(2);  // c12->Handle_2();
    fr.doSomeJob(3);  // c34->Handle_3();
    fr.doSomeJob(4);  // c34->Handle_4();

    return 0;
}
  1. To call a member function we need an object; so your maps should contain not simply method pointers, but something that can encapsulate a complete call: an object + a method pointer. That something is Delegate here.

  2. To make sure that the method is called correctly even if it's defined in a subclass, we need to store both the derived object and the method pointer type-correctly (no casting). So we make Delegate a template, with the derived class as its parameter.

  3. This means that delegates based on methods of different subclasses are incompatible, and cannot be put into a map. To work around this we introduce a common base class, DelegateBase, and the virtual function Call(). Call() can be called without knowing the exact type of stored object / method, and it will be dispatched to a type-correct implementation. Now we can store DelegateBase* pointers in the map.

Also check out boost::function and boost::bind, they provide a generalization for the above, and I think they could also be used to your purposes.

吻安 2024-12-05 02:59:03

此类问题有多种解决方案。

如果你有一个类可以处理几个不同的工作,在单独的
函数,最简单的解决方案是将其包装为几种类型,例如:

class JobsOneAndTwo
{
public:
    void doJobOne();
    void doJobTwo();
};

class JobOne : public AbstractJob, JobsOneAndTwo
{
public:
    virtual void doJob() { doJobOne(); }
};

class JobTwo : public AbstractJob, JobOneAndTwo
{
public:
    virtual void doJob() { doJobTwo(); }
};

如果这种情况经常出现在作业集中,您可以创建一个模板(通过
两个或更多成员函数指针)来生成单独的包装器
功能。

或者,您可以分派该类的数据成员:

class JobOneAndTwo : public AbstractJob
{
    int myJob;
public:
    JobOneAndTwo(int id) : myJob( id ) {}
    void JobOne();
    void JobTwo();
    virtual void doJob()
    {
        switch ( myJob ) {
        case 1:
            JobOne();
            break;

        case 2:
            JobTwo();
            break;
    }
};

在这种情况下,您实例化该类两次,每次传递一个
构造函数的不同参数。

在我见过的大多数情况下,当一个类可以处理两项工作时,这是
因为这两项工作仅在某些参数上有所不同;这真的只是
上面第二个解决方案的变体,除了您不切换到
调用不同的成员函数,您只需使用参数(传递
到基本函数中的构造函数中。

更一般地说,不要忘记您的具体工作类别可以有
数据,并且它们的行为可以通过这些数据来修改。并且你可以
使用不同的数据注册单个类的多个实例。

There are several solutions to this sort of problem.

If you have a class which can handle several different jobs, in separate
functions, the simplest solution is to wrap it, several types, e.g.:

class JobsOneAndTwo
{
public:
    void doJobOne();
    void doJobTwo();
};

class JobOne : public AbstractJob, JobsOneAndTwo
{
public:
    virtual void doJob() { doJobOne(); }
};

class JobTwo : public AbstractJob, JobOneAndTwo
{
public:
    virtual void doJob() { doJobTwo(); }
};

If this occurs often in the set of jobs, you can create a template (over
two or moer member function pointers) to generate the individual wrapper
functions.

Alternatively, you can dispatch on a data member of the class:

class JobOneAndTwo : public AbstractJob
{
    int myJob;
public:
    JobOneAndTwo(int id) : myJob( id ) {}
    void JobOne();
    void JobTwo();
    virtual void doJob()
    {
        switch ( myJob ) {
        case 1:
            JobOne();
            break;

        case 2:
            JobTwo();
            break;
    }
};

In this case, you instantiate the class twice, each time passing a
different argument to the constructor.

In most of the cases I've seen, when one class can handle two jobs, it's
because the two jobs differ only in some parameters; this is really just
a variant on the second solution above, except that you don't switch to
call different member functions, you simply use the parameters (passed
into the constructor) in the basic function.

More generally, don't forget that your concrete job classes can have
data, and their behavior can be modified by such data. And that you can
register multiple instances of a single class, with different data.

筑梦 2024-12-05 02:59:03

所以你说你有很多处理程序,每个处理程序可以处理任意数量的作业ID,并且你想注册任意数量的处理程序并让所有适用的处理程序处理给定的作业。

为此,让每个处理程序实现此接口:

struct Handler
{
  virtual bool canHandle(job_id_t id) const = 0;
  virtual void doJob(job_it_t id) = 0;
};

要注册处理程序,只需将指针存储在容器中:

std::vector<Handler*> handlers;

然后,如果您需要执行某项工作,请迭代容器并分派:

handleJob(job_it_t id)
{
  for (std::vector<Handler*>::iterator it = handlers.begin(), end = handlers.end(); it != end; ++it)
  {
    if ((*it)->canHandle(id))
      (*it)->doJob(id);
  }
}

So you say that you have many handlers, each of which can handle an arbitrary number of job IDs, and you want to register an arbitrary number of handlers and let all of them which apply handle a given job.

To that end, let every handler implement this interface:

struct Handler
{
  virtual bool canHandle(job_id_t id) const = 0;
  virtual void doJob(job_it_t id) = 0;
};

To register a handler, simply store a pointer in a container:

std::vector<Handler*> handlers;

Then, if you need to do a job, iterate the container and dispatch:

handleJob(job_it_t id)
{
  for (std::vector<Handler*>::iterator it = handlers.begin(), end = handlers.end(); it != end; ++it)
  {
    if ((*it)->canHandle(id))
      (*it)->doJob(id);
  }
}
记忆之渊 2024-12-05 02:59:03
typedef void (IAccaptable::*SOME_KIND_OF_FUNCTION_POINTER)(); 
...
Register(1, (SOME_KIND_OF_FUNCTION_POINTER)(&Class12::Handle1)); 

警告:此 C 风格强制转换仅适用于单一继承。 (嗯,实际上,多重继承也可以很好地编译,但是当使用指向非第一个基的成员函数的 funcPtr 调用 (衍生对象->*funcPtr)() 时类,那么它将在没有正确调整衍生对象指针以指向属于该基类的正确子对象的情况下调用它,很可能导致崩溃。)

更好但更复杂的解决方案是注册小型调用者对象,而不是注册小型调用者对象成员函数指针。当调用处理函数时,这些调用者对象可以适当地转换目标对象。

class CallerBase
{
public: 
    virtual void Call(Base* object) = 0; 
}; 

template <class Derived>
struct Caller: public CallerBase
{
public: 
    typedef void (Derived::*Function)(); 
    Caller(Function f): func(f) {}
    virtual void Call(Base* object) 
    {
        Derived* derived = static_cast<Derived*>(object); 
        (derived->*func)(); 
    }

private: 
    Function func; 
}; 

Register(1, new Caller<Derived>(&Derived::F)); 

然后你的映射将包含 CallerBase* 指针,一旦找到正确的调用者,你就可以执行caller->Call(object)。如果此调用中的 object 是 Derived*,则它将隐式转换为 Base*,但虚拟 Caller::Call() 函数会将其转换为 Base*在实际调用该方法之前返回 Derived*。

typedef void (IAccaptable::*SOME_KIND_OF_FUNCTION_POINTER)(); 
...
Register(1, (SOME_KIND_OF_FUNCTION_POINTER)(&Class12::Handle1)); 

Warning: this C-style cast will only work with single inheritance. (Well, actually the cast would compile just fine with multiple inheritance too, but when calling (derivedObject->*funcPtr)() with a funcPtr that points at a member function of a non-first base class, then it would be called without the derivedObject pointer having been properly adjusted to point at the proper subobject belonging to that base, most probably resulting in a crash.)

A better, but more complicated solution would be to register small caller objects instead of member function pointers. When calling the handler functions, these caller objects could appropriately cast the target object.

class CallerBase
{
public: 
    virtual void Call(Base* object) = 0; 
}; 

template <class Derived>
struct Caller: public CallerBase
{
public: 
    typedef void (Derived::*Function)(); 
    Caller(Function f): func(f) {}
    virtual void Call(Base* object) 
    {
        Derived* derived = static_cast<Derived*>(object); 
        (derived->*func)(); 
    }

private: 
    Function func; 
}; 

Register(1, new Caller<Derived>(&Derived::F)); 

Then your map would contain CallerBase* pointers, and once you find the proper caller, you'd do caller->Call(object). If object in this call is a Derived*, then it will be implicitly cast to Base*, but the virtual Caller<Derived>::Call() function will cast it back to Derived* before actually calling the method.

梦毁影碎の 2024-12-05 02:59:03

方法指针可以很有趣。

我不想自我推销,但请查看我在学校写的关于它们的指南。

http://nicolong.com/code-examples/menu-object-tutorial

可能会有所帮助。

Method pointers can be a lot of fun.

I don't want to self promote myself but check out my guide on them I wrote back in school.

http://nicolong.com/code-examples/menu-object-tutorial

Might help a little.

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