can't initialize functor objects when passing derived class in C++

巧了我就是萌 提交于 2020-01-23 02:15:07

问题


This question stems from a previous question I asked here. I cannot use any external libraries or the C++ 11 spec. Meaning I can't use std::bind, std::function, boost::bind,boost::function etc. I have to write it myself. The issue is the following:

Consider the code:

EDIT

Here is a complete program that exhibits the problem as requested:

#include <map>
#include <iostream>


class Command {    
public:
    virtual void executeCommand() = 0;
};

class Functor {
public:
    virtual Command * operator()()=0; 
};

template <class T> class Function : public Functor {
private:
    Command * (T::*fptr); 
    T* obj;                  
public:
    Function(T* obj, Command * (T::*fptr)()):obj(obj),
        fptr(fptr) {}

    virtual Command * operator()(){
        (*obj.*fptr)();   
    }            
};

class Addition:public Command {
public:
    virtual void executeCommand(){
        int x;
        int y;
        x + y;
    }
};

class CommandFactory {
public:
    virtual Addition * createAdditionCommand() = 0;
};

class StackCommandFactory: public CommandFactory {
private:
    Addition * add;
public:
    StackCommandFactory():add(new Addition()) {}

    virtual Addition * createAdditionCommand(){
         return add;
    }
};

void Foo(CommandFactory & fact) {
    Function<CommandFactory> bar(&fact,&CommandFactory::createAdditionCommand);
}

int main() {
    StackCommandFactory fact;
    Foo(fact);
    return 0;
}

The error it gives is "no instance of constructor "Function<T>::Function [with T=CommandFactory] matches the argument list, argument types are: (CommandFactory *, Addition * (CommandFactory::*)())

I think it's complaining because I'm passing it a derived type. I have to use pointers/references to the abstract classes because fact may not be a StackCommandFactory later down the road.

I can't say:

void Foo(CommandFactory & fact){
      Function<CommandFactory> spf(&fact,&fact.createAdditionCommand); //error C2276

}

because of then I receive error C2276 which says (as in the question I linked to) '&' : illegal operation on bound member function expression.

So explicitly my question is: "How do I initialize this functor object so that I can use it with the above mentioned interfaces?"


回答1:


Here's a modification of my original answer that seems to do what you need, without using the any functor stuff from C++11 or boost.

#include <vector>
#include <map>
#include <string>

struct Command {};
struct Subtract : Command {};
struct Add : Command {};

class CommandFactory
{
  public:

    virtual Subtract * createSubtractionCommand() = 0;
    virtual Add * createAdditionCommand() = 0;
};

class StackCommandFactory : public CommandFactory
{
  public:

    virtual Subtract * createSubtractionCommand(void);
    virtual Add * createAdditionCommand(void);

    Subtract * sub;
    Add * add;
};

Subtract * StackCommandFactory::createSubtractionCommand(void) { return sub; }
Add * StackCommandFactory::createAdditionCommand(void) { return add; }

class CommandGetterImpl
{
  public:
    virtual CommandGetterImpl* clone() const=0;
    virtual Command* get()=0;
    virtual ~CommandGetterImpl() {};
};

class CommandGetter
{
  public:
  Command* get() { return impl_->get(); }
  ~CommandGetter() { delete impl_; }
  CommandGetter( const CommandGetter & other ) : impl_(other.impl_?other.impl_->clone():NULL) {}
  CommandGetter& operator=( const CommandGetter & other ) {
     if (&other!=this) impl_= other.impl_?other.impl_->clone():NULL;
     return *this;
  }
  CommandGetter() : impl_(NULL) {}
  CommandGetter( CommandGetterImpl * impl ) : impl_(impl) {}
  CommandGetterImpl * impl_;
};

class Parser
{
  public:
    Parser (CommandFactory & fact);

    std::map<std::string, CommandGetter > operations; 
};

template<typename MEMFN, typename OBJ >
class MemFnCommandGetterImpl : public CommandGetterImpl
{
  public:
  MemFnCommandGetterImpl(MEMFN memfn, OBJ *obj) : memfn_(memfn), obj_(obj) {}
  MemFnCommandGetterImpl* clone() const { return new MemFnCommandGetterImpl( memfn_, obj_) ; }
  Command* get() { return (obj_->*memfn_)(); }
  MEMFN memfn_;
  OBJ * obj_;
};


template< typename MEMFN, typename OBJ >
CommandGetter my_bind( MEMFN memfn, OBJ * obj )
{
  return CommandGetter( new MemFnCommandGetterImpl<MEMFN,OBJ>(memfn,obj) );
};

Parser::Parser(CommandFactory & fact)
{
  operations["+"] = my_bind(&CommandFactory::createAdditionCommand, &fact);
  operations["-"] = my_bind(&CommandFactory::createSubtractionCommand, &fact);
}

#include <iostream>
int main()
{
  Add add;
  Subtract sub;

  StackCommandFactory command_factory;
  command_factory.add = &add;
  command_factory.sub= &sub;
  Parser parser(command_factory);

  std::cout<<"&add = "<<&add<<std::endl;
  std::cout<<"Add = " <<  parser.operations["+"].get() <<std::endl;
  std::cout<<"&sub = "<<&sub<<std::endl;
  std::cout<<"Sub = " <<  parser.operations["-"].get() <<std::endl;

  return 0;
}



回答2:


You need an explicit cast on the 2nd parameter of the bar instance:

Function<CommandFactory> bar(&fact,
  reinterpretet_cast<Command *(CommandFactory::*)()>(&CommandFactory::createAdditionCommand));

Besides, you're missing parens for the method pointer attribute in Function:

Command * (T::*fptr)();

This error might have prevented you to find the solution above.

You are also missing the return keyword in the operator() there (a mistake that I often do because of my functional programming habits):

virtual Command * operator()(){
   return  (obj->*fptr)();
}            

You can avoid the cast by making the return type a template parameter:

template <class T, typename D> 
class Function : public Functor {
private:
    D * (T::*fptr); 
    T* obj;
public:
    Function(T* obj, D * (T::*fptr)()): obj(obj),  fptr(fptr){}
    virtual Command * operator()(){
        return (obj->*fptr)();
    }            
};

void Foo(CommandFactory & fact){
    Function<CommandFactory, Addition> bar(&fact, &CommandFactory::createAdditionCommand);
}

Note that I did not templatize Functor after all. While it seemed a good idea at first to me, it make things a bit more complex. If you wish to make Functor a template too, the return type will have to be exactly the same, you cannot use an inheritance relation between them, unless you make them both parameters of the Function template. As a rule of thumb, whenever you stumble on a template issue like that, remember that template are like C macros at the core, it's a rewriting mechanism, which will expand the template into real C++ types (functions or classes) separately. You can picture the problem that way:

template <typename T, typename D>
class Function : public Functor<D> { /* ... */ };

will be expanded to

class Function<CommandFactory, Addition> : public Functor<Addition> {
    /* ... */
};

Functor<Addition> and Functor<Command> bears no relationship at all; these are two different classes.

If C++ template did carry the notion of bounded polymorphism (like in Java or C#), it could have perhaps been possible to write it in way close to your intent.

I recommend:

  • keeping the Functor a simple class, to make the code simpler to work with for the time being, and
  • if the need arises later on, trying to refactor a working version with that new feature.



回答3:


Generally speaking, it's a bad idea to use member function pointers as opposed to std::function. More generally,

typedef std::function<void()> Command;
typedef std::function<Command()> Functor;

Really, there's absolutely no need whatsoever for any member function pointers in your code.



来源:https://stackoverflow.com/questions/15733390/cant-initialize-functor-objects-when-passing-derived-class-in-c

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!