简体   繁体   中英

c++ call method on abstract class returned by a function

I thing I'm missing something obvious but here is my problem

with a pure abstract class IFoo

class IFoo
{
 public:
    virtual bool isBar1() const=0;
    virtual bool isBar2() const=0;
};

and 2 implementations

class Foo1 : public IFoo
{
 public:
    bool isBar1() const override { return true;}
    bool isBar2() const override { return false;}
};

class Foo2 : public IFoo
{
 public:
    bool isBar1() const override { return false;}
    bool isBar2() const override { return true;}
};

I have a managing class that must call the right method depending on a variable protocol

class FooManager : public IFoo
{
 public:
    bool isBar1() const override 
    {
      switch(protocol)
      {
        case 1: return Foo1().isBar1();
        case 2: return Foo2().isBar1();
        default: return false;
      }
    }
    bool isBar2() const override
    {
      switch(protocol)
      {
        case 1: return Foo1().isBar2();
        case 2: return Foo2().isBar2();
        default: return false;
      }
    }
    void setProtocol(int proto){this->protocol = proto;}
 private:
    int protocol{0};
};

But there is a bunch of methods and I don't want to put the switch(protocol) everywhere given that it's really repetitive and new FooX could be added at anytime.

How can I call the right override without using templates (given that protocol is dynamic and FooManager is persistent) and without using the heap on every call (through smart pointer or the likes because it's for an embedded project where we try to stay on the stack as much as possible).

I can't just create a getFoo() method that return IFoo because it's an abstract class And I can't return an IFoo& neither because it would return a reference to a temporary.

IFoo& FooManager::getFoo()
{
      switch(protocol)
      {
        case 1: return Foo1();
        case 2:
        default:  return Foo2();
      }
  //return reference to temporary
}

What else can I do?

You could return a unique_ptr, such as

std::unique_ptr<IFoo> FooManager::getFoo() {
    switch (protocol) {
        case 1: return std::make_unique<Foo1>();
        case 2: 
        default: return std::make_unique<Foo2>();
    }
}

This would result in the data being a pointer and polymorphism being applied on calling the member functions

You can return a std::unique_ptr so you get polymorphic behavior but can control the lifetime of the returned object.

std::unique_ptr<IFoo> FooManager::getFoo()
{
      switch(protocol)
      {
        case 1: return std::make_unique<Foo1>();
        case 2:
        default:  return std::make_unique<Foo2>();
      }
}

Since you have a very specific requirement I suggest a very specific solution for this exact problem (which may not be suitable elsewhere). In order to avoid having to use dynamic allocation and pointers or references you can "fake" polymorphism using function pointers. A small example given the requirements you mentioned in your comments:

class Foo {
    public:
    // function pointer aliases to make them easier to use
    // I opted to make the two functions take different parameters for demonstration purposes
    using isBar1Func = bool(*)(const Foo*);
    using isBar2Func = bool(*)(int);
    // constructor requiring the function pointers as parameters
    Foo(int value, isBar1Func bar1func, isBar2Func bar2func) : 
        m_value(value), m_bar1Func(bar1func), m_bar2Func(bar2func) {}

    bool isBar1() const {
        return m_bar1Func(this);
    }

    bool isBar2() {
        return m_bar2Func(m_value);
    }

    int getValue() const {
        return m_value;
    }

    private:
       int m_value;
       isBar1Func m_bar1Func;
       isBar2Func m_bar2Func;
};

// example functions to be passed into the constructor
static bool testBar1Func(const Foo* foo) {
    return foo->getValue() != 0;
}

static bool testBar2Func(int value) {
    return value > 1;
}

// getFoo can simply return a copy
Foo FooManager::getFoo() {
    switch (protocol) {
        case 1: return Foo(1, testBar1Func, testBar2Func);
        // also works with non-capturing lambdas, which can be converted to function pointers
        case 2: return Foo(2, 
                           [](const Foo* foo) { return foo->getValue() != 1; },
                           [](int value) {return value != 12; });
        // add remaining cases as desired
    }
}

Thanks to @UnholySheep response, here is what I ended with:

class FooManager : public IFoo{
  public:
     using FooFunc = bool(*)(const IFoo&);

     bool callFoo(FooFunc function) const{
        switch(protocol) {
          case 1: return function(Foo1());
          case 2: return function(Foo2());
          //and the other cases
        }
    }
    bool isBar1() const override {
       return callFoo([](const IFoo& foo){return foo.isBar1();});
    }
    bool isBar2() const override {
       return callFoo([](const IFoo& foo){return foo.isBar2();});
    }
};

my FooX classes stay the sames and the switch(protocol) is in a single function meaning that if a new protocol arrives, I just have to create a new FooY for that protocol and add it to the switch to get it working. All that with compile time checks and no use of the heap. Thanks again @UnholySheep and the others as well.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM