返回一个Type,或者如何保留一个对象指针的类型?

时间:2014-01-23 16:14:02

标签: c++ pointers inheritance c++11 rtti

我有一个非常复杂的代码结构,但重要的是:

典型的设置:我有一个基类和两个派生自这个基类的类,每个类都有自己的成员,并且没有标准的构造函数

class BaseSolver{
...
};

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

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

现在我有一个config xml文件,我从中读取是否必须使用SolverASolverB。因此我有一个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);
    }
};

在我的应用程序的某个地方(为简单起见,我们说它是main.cpp):

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

这绝对没问题。

但是现在,主要中的我必须分别访问派生类ab的成员。 我该怎么做?

我想过只从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;
    }
};

但我当然不能将decltype指定为返回值。

我真的很无奈。我希望任何暗示正确的方向,甚至是解决这个问题的方法。

[编辑]:派生的解算器类需要的不仅仅是xml文件中的信息才能正常工作。这意味着,我必须设置一些来自网格文件的属性。所以我可以将meshfile提供给IOService,以便IOService可以这样设置适当的成员:

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;
        }
    }
};

但是IOService需要知道我要避免的类MeshType,因为我认为它破坏了封装。 所以我想分别在我的程序的另一部分设置成员ab(这里为了简单起见)。

考虑到这一点,只有Daniel Daranas的答案对我来说似乎是一个解决方案。但我想避免动态演员。

所以重新提出的问题可能是:我应该如何更改设计以确保封装并避免动态转换? [/ Edit]

我正在使用clang 3.4 ob ubuntu 12.04 lts。

3 个答案:

答案 0 :(得分:3)

使用dynamic_cast尝试将指向基类的类转换为指向派生类的指针。如果基类的指向对象不存在(基指针的NULL值),或者实际上不是派生类对象,它将返回NULL。如果结果不是NULL,则表示有一个有效的指向派生类的指针。

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

请注意,可能有一些比使用dynamic_cast更好的设计解决方案。但至少这是一种可能性。

答案 1 :(得分:2)

关于多态性的有趣之处在于,当你不使用它时,它指出了你。

以您的服务方式继承基类1目的:为具有不同行为的对象公开统一接口。基本上,您希望子类看起来一样。如果我有继承自A的B和C类,我想对该课说“do foo”,它会foobfooc

基本上,你正在翻转它:我有一个A型的B和C,如果它是B,我想做foob,如果是C,我想做fooc。虽然这看起来很可怕,但通常解决问题的最佳方法是重新解释这个问题。

因此,对于您的示例,您当前正在说“好的,所以我有一个XML文件,如果我正在制作A,我将以一种方式从中读取数据,或者如果我制作B,我会以其他方式读取数据。 “但多态方式将是“我有一个XML文件。它告诉我创建A或B,然后我告诉实例解析XML文件”。

因此,解决此问题的方法之一是更改求解器界面:

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

虽然这会以一种使用多态的方式对问题进行重新定义,并且无需您查看已创建的内容,但您可能不喜欢这样,出于同样的原因我不这样做:您有提供默认构造函数,使类处于未知状态。

因此,不是在接口级强制执行它,而是可以在构造函数级别强制执行它,并使SolverA和SolverB都必须将XML字符串作为构造函数的一部分。

但是,如果XML字符串不好怎么办?然后你会在构造函数中得到一个错误状态,这也是一个禁忌。所以我将使用工厂模式来解决这个问题:

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;
  }
};

我没有在这里放任何实际的XML处理,因为我很懒,但你可以让工厂从主标签中获取类型,然后传递其余的节点。这种方法确保了很好的封装,可以捕获xml文件中的错误,并安全地分离您尝试获取的行为。它还只将危险函数(默认构造函数和ReadXMLFile)暴露给SolverFactory,在那里你(据说)知道你在做什么。

编辑:回答问题

你说过的问题是“我有A型B和C,如果是B我想设置”b“设置,如果是C,我想设置”c“设置”。

利用多态性,你说“我有一个A型的B和C。我告诉他们得到他们的设置。”

有几种方法可以做到这一点。如果你不介意用类修改你的IO,你可以简单地公开方法:

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

然后为每个类创建单独的方法。

如果你想分开创建它们,那么你想要的是io中的多态性。所以以这种方式揭露它:

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

示例实施

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;
};

乍一看,这似乎是很多代码(我们将类的数量增加了一倍,并且可能需要为BaseSolver和IO接口提供工厂类)。为什么呢?

这是可扩展性/可维护性的问题。让我们说你已经想出了一个你想要添加的新解算器(D)。如果您使用动态强制转换,则必须找到顶级中的所有位置并添加新的case语句。如果只有一个地方,那么这很容易,但如果它是10个地方,你很容易忘记一个,很难追查。相反,使用此方法,您将拥有一个单独的类,该类具有解算器的所有特定IO功能。

让我们想一想当解算器的数量增长时,那些dynamic_cast检查会发生什么。你已经和一个庞大的团队一起维护这个软件多年了,并且假设你已经提出了字母Z之前的求解器。每个if-else语句现在都是数百 - 几千行:如果你在O中有错误你必须滚动浏览AM只是为了找到错误。此外,使用多态的开销是不变的,而反射只会增长,增长和增长。

这样做的最后好处是,如果你有一个class BB : public B。您可能拥有B中的所有旧设置,并希望保留它们,只需将其设置得更大一些。使用此模型,您可以扩展IO类以及io for BB并重用该代码。

答案 2 :(得分:1)

实现此目的的一种方法是在基类中添加接口方法:

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*/};
};

主要是:

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