I have a very complicated code structure, but the important bits are:

typical setup: I have a base class and two classes that derive from this base class and each has own members, and which don't have a standard constructor

class BaseSolver{
...
};

class SolverA : BaseSolver{
public:
    std::string a;
    SolverA(TypeA objectA);
};

class SolverB : BaseSolver{
public:
    int b;
    SolverB(TypeB objectB);
};

Now I have a config xml file from which I read whether I have to use SolverA or SolverB. Therefore I have an IOService:

template<class T>
class IOService
{
    BaseSolver* getSolver()
    {
        std::string variableThatIReadFromXML;

        /* here I have to perform many actions before I can create a solver object
         * to retrieve the data needed for the constructors */

        TypeA variableIConstrucedWithDataFromXML;
        TypeB anotherVariableIConstrucedWithDataFromXML;

        if (variableThatIReadFromXML == "a")
            return new SolverA(variableIConstrucedWithDataFromXML); // I know that this can leak memory
        else if (variableThatIReadFromXML == "b")
            return new SolverB(anotherVariableIConstrucedWithDataFromXML);
    }
};

And somewhere in my application (for simplicity let's say it's the main.cpp):

int main(){
    IOService ioService;
    BaseSolver* mySolver = ioService.getSolver();
}

That is absolutely fine.

But now, in the main I have to access the members of the derived classes a and b respectively. How can I do this?

I thought of retreving only the type of the Solver from the IOService:

class IOService
{
    decltype getSolverType()
    {
        std::string variableThatIReadFromXML;

        /* here I have to perform many actions before I can create a solver object
         * to retrieve the data needed for the constructors */

        TypeA variableIConstrucedWithDataFromXML;
        TypeB anotherVariableIConstrucedWithDataFromXML;

        if (variableThatIReadFromXML == "a")
            return new SolverA(variableIConstrucedWithDataFromXML); // I know that this can leak memory
        else if (variableThatIReadFromXML == "b")
            return new SolverB(anotherVariableIConstrucedWithDataFromXML);
    }

    TypeA getConstructorDataForSolverA()
    {
        /* here I have to perform many actions before I can create a solver object
         * to retrieve the data needed for the constructors */

        return variableIConstrucedWithDataFromXML;
    }


    TypeB getConstructorDataForSolverB()
    {
        /* here I have to perform many actions before I can create a solver object
         * to retrieve the data needed for the constructors */

        return anotherVariableIConstrucedWithDataFromXML;
    }
};

But of course I can't specify decltype as return value.

I'm really helpless. I would appreciate any hint into the right direction, or even a solution for this problem.

[Edit]: The derived solver classes need more than only the information from the xml file to work properly. That means, that I have to set some more properties which come from a mesh file. So I could give the meshfile to the IOService, so that the IOService could set the appropriate members this way:

class IOService
{
    BaseSolver* getSolver(MeshType myMesh)
    {
        std::string variableThatIReadFromXML;

        /* here I have to perform many actions before I can create a solver object
         * to retrieve the data needed for the constructors */

        TypeA variableIConstrucedWithDataFromXML;
        TypeB anotherVariableIConstrucedWithDataFromXML;

        if (variableThatIReadFromXML == "a")
        {
            auto solverA = new SolverA(variableIConstrucedWithDataFromXML); // I know that this can leak memory
            solverA.a = mesh.a;
        }
        else if (variableThatIReadFromXML == "b")
        {
            auto solverB = new SolverB(anotherVariableIConstrucedWithDataFromXML);
            solverB.b = mesh.b;
        }
    }
};

But then the IOService needs to know the class MeshType, what I want to avoid, because I think that it breaks encapsulation. So I wanted to set the member a and b, respectively, in another part of my program (here for simplicity in the main).

Taking this into account, only the answer from Daniel Daranas seems like a solution for me. But I wanted to avoid dynamic casts.

So a reformulated question could be: How should I change my design to ensure encapsulation and avoid dynamic casts? [/Edit]

I am using clang 3.4 ob ubuntu 12.04 lts.

有帮助吗?

解决方案 2

The funny thing about polymorphism is that it points out to you when you are not using it.

Inheriting a base class in the way you are serves 1 purpose: to expose a uniform interface for objects with different behaviors. Basically, you want the child classes to look the same. If I have classes B and C that inherit from A, I want to say "do foo" to the class, and it'll do foob or fooc.

Essentially, you're flipping it around: I have a B and C of type A, and if it is B i want to do foob and if it is C I want to do fooc. While this may seem scary, usually the best way to solve the problem is to rephrase the question.

So to your example, you are currently saying "OK, so I have an XML file, and I will read data from it one way if I'm making an A, or another way if I'm making a B." But the polymorphic way would be "I have an XML file. It tells me to make an A or a B, and then I tell the instance to parse the XML file".

So one of the ways to solve this to change your solver interface:

class BaseSolver
{
public:
  virtual void ReadXMLFile(string xml) = 0;
...
};

While this does rephrase the problem in a way that uses polymorphism, and removes the need for you to see what you've created, you probably don't like that for the same reason I don't: you'd have to supply a default constructor, which leaves the class in an unknown state.

So rather than enforce it at the interface level, you could enforce it at the constructor level, and make both SolverA and SolverB have to take in the XML string as part of the constructor.

But what if the XML string is bad? Then you'd get an error state in the constructor, which is also a no-no. So I'd deal with this using the factory pattern:

class SolverFactory;

class BaseSolver
{
public:
  virtual void solve() = 0;
protected:
  virtual int ReadXML(std::string xml) = 0;
  friend class SolverFactory;
};

class A : public BaseSolver
{
public:
  virtual void solve() {std::cout << "A" << std::endl;}
protected:
  A(){}
  virtual int ReadXML(std::string xml) {return 0;}
  friend class SolverFactory;
};

class B : public BaseSolver
{
public:
  virtual void solve() {std::cout << "B" << std::endl;}
protected:
  B(){}
  virtual int ReadXML(std::string xml) {return 0;}
  friend class SolverFactory;
};

class SolverFactory
{
public:
  static BaseSolver* MakeSolver(std::string xml)
  {
    BaseSolver* ret = NULL;
    if (xml=="A")
    {
      ret = new A();
    }
    else if (xml=="B")
    {
      ret = new B();
    }
    else
    {
      return ret;
    }
    int err = ret->ReadXML(xml);
    if (err)
    {
      delete ret;
      ret = NULL;
    }
    return ret;
  }
};

I didn't put any actual XML processing in here because I am lazy, but you could have the factory get the type from the main tag and then pass the rest of the node in. This method ensures great encapsulation, can catch errors in the xml file, and safely separates the behaviors you are trying to get. It also only exposes the dangerous functions (the default constructor and ReadXMLFile) to the SolverFactory, where you (supposedly) know what you are doing.

Edit: in response to the question

The problem you've stated is "I have a B and C of type A, and if is B i want to set "b" settings and if it is C i want to set "c" settings".

Taking advantage of polymorphism, you say "I have a B and C of type A. I tell them to get their settings."

There a couple of ways to do this. If you don't mind mangling your IO with the class, you can simply expose the method:

class BaseSolver
{
public:
  virtual void GetSettingsFromCommandLine() = 0;  
};

And then create the individual methods for each class.

If you do want to create them separate, then what you want is polymorphism in the io. So expose it that way:

class PolymorphicIO
{
public:
  virtual const BaseSolver& get_base_solver() const = 0;
  virtual void DoSettingIO() = 0;
};

an example implmentation

class BaseSolverBIO : PolymorphicIO
{
public:
  virtual const BaseSolver& get_base_solver() const {return b;}
  virtual void DoSettingIO() { char setting = get_char(); b.set_b(setting);}
private:
  BaseSolverB b;
};

At first glance this seems like a lot of code (we've doubled the number of classes, and probably need to supply a factory class for both BaseSolver and the IO interface). Why do it?

It is the issue of scaleability/maintainability. Lets say you have figured out a new solver you want to add (D). If you are using dynamic cast, you have to find all the places in your top level and add a new case statement. If there is only 1 place, then this is pretty easy, but if it is 10 places, you could easily forget one and it would be hard to track down. Instead, with this method you have a separate class that has all the specific IO functionality for the solver.

Lets also think of what happens to those dynamic_cast checks as the number of solvers grows. You've been maintaining this software for years now with a large team, and lets say you've come up with solvers up to the letter Z. Each of those if-else statements are hundreds-a tousand of lines long now: if you have an error in O you have to scroll through A-M just to find the bug. Also, the overhead for using the polymorphism is constant, while reflection just grows and grows and grows.

The final benefit for doing it this way is if you have a class BB : public B. You probably have all the old settings from B, and want to keep them, just make it a little bigger. Using this model, you can extend the IO class as well for the io for BB and reuse that code.

其他提示

Use dynamic_cast to try to cast a pointer-to-base-class to pointer-to-derived-class. It will return NULL if the pointed-to object of the base class does not exist (NULL value of the base pointer), or is not actually a derived class object. If the result, instead, is not NULL, you have a valid pointer-to-derived-class.

int main(){
    IOService ioService;
    BaseSolver* mySolver = ioService.getSolver();
    SolverB* bSolver = dynamic_cast<SolverB*>(mySolver);
    if (bSolver != NULL)
    {
        int finallyIGotB = bSolver->b;
        cout << finallyIGotB;
    }
}

Note that there may be some better design solutions than using dynamic_cast. But at least this is one possibility.

One way to achieve this is to add an interface method into the base class:

class BaseSolver{
virtual void SolverMethodToCallFromMain() = 0;
...
};

class SolverA : BaseSolver{
public:
    std::string a;
    SolverA(TypeA objectA);
    virtual void SolverMethodToCallFromMain() {/*SolverA stuff here*/};
};

class SolverB : BaseSolver{
public:
    int b;
    SolverB(TypeB objectB);
    virtual void SolverMethodToCallFromMain() {/*SolverB stuff here*/};
};

And in main:

int main(){
    IOService ioService;
    BaseSolver* mySolver = ioService.getSolver();
    mySolver->SolverMethodToCallFromMain();
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top