0

Class A contains a pointer to abstract class B (only headers implemented):

// A.h
#include "B.h"
class A {
   public:
   A();
   virtual ~A();
   pointBto(B* b_);   // { this.b = b_;  }

   private:
   B* b;

}


// B.h
class B {
    public:
    B();
    virtual ~B();
    virtual void b_method() = 0;
}

Classes C and D inherit B.

// C.h
#include "B.h"
Class C : public B {
    public:
    C();
    ~C();
    virtual b_method();

}

// D.h
#include "B.h"
Class D : public B {
    public:
    D();
    ~D();
    virtual b_method();

}

Application reads a string and depending on that, creates a new object of class C or D and points b to the created object.

Note: I don't want to create an endless chain of

if (string_ == "C")
{
   a_.pointBto(new C());
}
else if (string_ == "D")
{
   a_.pointBto(new D());
}
else ...
roymcclure
  • 404
  • 3
  • 12

3 Answers3

1

Simply create an std::map<std::string, std::function<B*(/*...*/)>>:

static const std::map<std::string, std::function<B*(/*...*/)>> factory = {
    { "C", [](/*...*/){ return new C(/*...*/); },
    { "D", [](/*...*/){ return new D(/*...*/); },
};

a_.pointBto(factory[string_](/*arguments*/));
YSC
  • 38,212
  • 9
  • 96
  • 149
  • Looks good. How about passing parameters to the constructors? Would that be feasible? – roymcclure Jan 06 '16 at 14:25
  • I guess, OP means passing parameters which are dependent on the string. – SergeyA Jan 06 '16 at 14:30
  • @SergeyA you are more or less right. The same string contains several key/value pairs that are read into a std::map which is then passed to the constructor of the polymorphic classes. The constructor will then handle the parameters the right way. Then again i see no difference. – roymcclure Jan 06 '16 at 14:36
  • Actually, it's my bad - comments somehow triggered my tunnel vision and I failed to see arguments passed to lambdas, – SergeyA Jan 06 '16 at 14:38
  • @roymcclure just replace the `/*...*/` with whatever you need and you're good. – YSC Jan 06 '16 at 15:44
1

What you are looking for is often refered to as 'virtual constructor'. I am usually not a fan of this construct, but it can be implemented using, for example, 'clone' idiom. Something like following:

struct Parent {
    virtual Parent* clone(/*??? arg*/)  = 0;
};

struct Child1 : Parent {
    /*virtual */ Parent clone(/*??? arg*/) { return new Child1(/*arg*/); }
};
/* Child 2 */

Parent* make_parent(Choice arg) {
    static std::map<Choice, Parent*> factory({{ch1, new Child1()}, {ch2, new Child2()}};
    return factory.find(arg).second->clone(/**/);
}

The bigguest problem with that is that clone arguments are aften reduced to some sort of the blob, which requires casting.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • That looks terrific. Two noobish questions though: a) i assume clone() wouldn't need to be static only for your example because you are using structs instead of classes, and b) I guess I could get rid of casting since in my case parameters are always of the same type? What do I fail to see? – roymcclure Jan 06 '16 at 14:47
  • Also, you stated that you are not a fan of this construct. What would be a better approach in your opinion? – roymcclure Jan 06 '16 at 14:57
  • 1
    `clone` is not static, it is `virtual`, and can not be static. I am using structs simply to have default public access modificator and inheritance, other than that it is no different from class. If all your arguments are the same type for all descendants, than yes, no casting required. – SergeyA Jan 06 '16 at 14:58
  • 1
    @roymcclure, in my, purely subjective, experience you will end up casting to the actual type somewhere down the road. Again, it is my experience only, but I am seeing over and over again that whenever a factory method is employed, the real type of the object is needed sooner or later. Call it a prejudice. – SergeyA Jan 06 '16 at 14:59
  • I believe this is not the case. The application runs a loop where the same methods in all the polymorphic objects are called with every iteration by making a call to the parent class' methods, and that's the end of it. If the compiler internally makes any casting, that's beyond my knowledge. But AFAIK virtual methods are called via vtables and no casting is made there, please correct me if i'm wrong. – roymcclure Jan 06 '16 at 15:07
  • 1
    @roymcclure, virtual methods are totally fine, and you should not worry about them. I am talking about user-triggered casting, because sooner or later developer finds out that they need to call some special methods which are not part of the parent class for certain type of classess. Like I said, it is just my personal experience, your app might not follow this suit. – SergeyA Jan 06 '16 at 15:16
1

If you want to avoid having an if else block (or some other similar centralized mechanism) which you extend every time you add a new derived class, you can have the derived types register themselves.

Check this thread for a trick on how to do it:

Is there a way to instantiate objects from a string holding their class name?

(note that with the solution given, the string doesn't need to be the same as the class name). Essentially you still have a map from a string to a factory function i.e.

map< std::string, std::function<B*()> >

the difference is that the code responsible for adding the callback to the map is where the derived class is defined.

Community
  • 1
  • 1
opetroch
  • 3,929
  • 2
  • 22
  • 24