返回类型,或如何保留对象指针的类型



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

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

class BaseSolver{
...
};
class SolverA : BaseSolver{
public:
    std::string a;
    SolverA(TypeA objectA);
};
class SolverB : BaseSolver{
public:
    int b;
    SolverB(TypeB objectB);
};

现在,我有一个配置XML文件,我从中读到我必须使用SolverA还是SolverB。因此,我有一个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文件的信息才能正常工作。这意味着,我必须设置来自网格文件的更多属性。因此,我可以将网格列为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。

使用 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相比,可能有一些更好的设计解决方案。但至少这是一种可能性。

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

以您的服务方式继承一个基类的目的:揭露具有不同行为的对象的统一接口。基本上,您希望孩子课看起来相同。如果我有从A继承的B类B和C,我想对类说" DO foo",并且它将进行foobfooc

本质上,您正在将其翻转:我有A型的B和C,如果是B,我想做foob,如果是C,我想做fooc。虽然这似乎令人恐惧,但解决问题的最佳方法是重新提出问题。

因此,在您的示例中,您目前在说:"好的,所以我有一个XML文件,如果我要制作A,则我会以B的方式读取数据。"但是多态方式将是"我有一个XML文件。它告诉我制作A或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)暴露于求解功能,您(据称)知道您在做什么。

编辑:回答问题

您所说的问题是"我有A型B和c,如果是BP>

利用多态性的优势,您说:"我有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)。如果您使用的是动态铸件,则必须在顶级中找到所有位置并添加新的案例语句。如果只有1个地方,那么这很容易,但是如果它是10个地方,您很容易忘记一个地方,很难追踪。相反,使用此方法,您拥有一个单独的类,该类具有求解器的所有特定IO功能。

当求解器的数量增长时,还要考虑一下那些动态_cast检查会发生什么。您已经与一个大型团队一起维护了多年的软件,可以说您已经提出了求解器。在o中有一个错误,您必须滚动浏览A-M才能找到错误。同样,使用多态性的开销是恒定的,而反射只是成长,增长和成长。

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

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

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

最新更新