【问题标题】:return a Type, or how to preserve a type of an object pointer?返回一个类型,或者如何保存一个对象指针的类型?
【发布时间】:2014-02-14 07:42:27
【问题描述】:

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

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

class BaseSolver{
...
};

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

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

现在我有一个配置 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 中检索 Solver 的类型:

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。

【问题讨论】:

  • 您是在问如何将基类指针向下转换为相应的派生类指针?
  • 使用虚函数怎么样? IE。使用BaseSolver 作为接口,main 可以使用它来访问派生求解器的功能。

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


【解决方案1】:

使用dynamic_cast 尝试将指向基类的指针转换为指向派生类的指针。如果基类的指向对象不存在(基指针的 N​​ULL 值),或者实际上不是派生类对象,它将返回 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 更好的设计解决方案。但至少这是一种可能性。

【讨论】:

  • @Rico-E 一种抽象,它捕获客户看到的后代的共同属性。
  • 感谢提出修改建议的用户。我修复了代码中的错误。
【解决方案2】:

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

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

本质上,你是在翻转它:我有一个类型为 A 的 B 和 C,如果是 B,我想做 fob,如果是 C,我想做 foc。虽然这看起来很可怕,但通常解决问题的最佳方法是重新表述问题。

因此,对于您的示例,您当前是在说“好的,所以我有一个 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.xml 中的多态性。所以就这样暴露吧:

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 功能。

让我们也想想随着求解器数量的增加,这些 dynamic_cast 检查会发生什么。你已经和一个大团队一起维护这个软件多年了,假设你已经想出了字母 Z 的求解器。这些 if-else 语句中的每一个现在都有数百行——如果你在 O 中有错误,您必须滚动浏览 AM 才能找到错误。此外,使用多态性的开销是恒定的,而反射只会不断增长。

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

【讨论】:

  • 感谢您的详细解释。这似乎是一个很好的解决方案,但我如何在这个工厂之外设置属性ab?请参阅我的编辑以获取更多信息。
【解决方案3】:

实现这一点的一种方法是在基类中添加一个接口方法:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多