返回一个Type,或者如何保留一个类型的对象指针? [英] return a Type, or how to preserve a type of an object pointer?

查看:153
本文介绍了返回一个Type,或者如何保留一个类型的对象指针?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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

  class BaseSolver {
...
};

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

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

现在我有一个config xml文件,我从中读取是否必须使用 SolverA SolverB 。因此我有一个IOService:

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

/ *这里我必须执行许多操作,然后才能创建一个solver对象
*来检索构造函数所需的数据* /

TypeA variableIConstrucedWithDataFromXML;
TypeB anotherVariableIConstrucedWithDataFromXML;

if(variableThatIReadFromXML ==a)
返回新的SolverA(variableIConstrucedWithDataFromXML); //我知道这可能泄漏内存
else if(variableThatIReadFromXML ==b)
return new SolverB(anotherVariableIConstrucedWithDataFromXML);
}
};

在我的应用程序中的某处(简单地说,它是main.cpp):

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

这绝对不错。



但现在,在主中,我必须访问派生类的成员 a b
我如何做到这一点?



我想只从IOService中撤消求解器的类型:

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

/ *这里我必须执行许多操作,然后才能创建一个solver对象
*来检索构造函数所需的数据* /

TypeA variableIConstrucedWithDataFromXML;
TypeB anotherVariableIConstrucedWithDataFromXML;

if(variableThatIReadFromXML ==a)
返回新的SolverA(variableIConstrucedWithDataFromXML); //我知道这可能泄漏内存
else if(variableThatIReadFromXML ==b)
return new SolverB(anotherVariableIConstrucedWithDataFromXML);
}

TypeA getConstructorDataForSolverA()
{
/ *这里我必须执行许多操作,然后才能创建一个solver对象
*来检索构造函数所需的数据* /

return variableIConstrucedWithDataFromXML;
}


TypeB getConstructorDataForSolverB()
{
/ *这里我必须执行许多操作才能创建一个solver对象
*来检索构造函数所需的数据* /

return anotherVariableIConstrucedWithDataFromXML;
}
};

但是我不能指定 decltype 作为返回值。



我真的很无奈。



[编辑]:派生的求解器类需要更多的比只有来自xml文件的信息才能正常工作。这意味着,我必须设置一些来自网格文件的属性。所以我可以给IOService网格文件,以便IOService可以通过这种方式设置适当的成员:

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

/ *这里我必须执行许多操作,然后才能创建一个solver对象
*来检索构造函数所需的数据* /

TypeA variableIConstrucedWithDataFromXML;
TypeB anotherVariableIConstrucedWithDataFromXML;

if(variableThatIReadFromXML ==a)
{
auto solverA = new SolverA(variableIConstrucedWithDataFromXML); //我知道这可以泄漏内存
solverA.a = mesh.a;
}
else if(variableThatIReadFromXML ==b)
{
auto solverB = new SolverB(anotherVariableIConstrucedWithDataFromXML);
solverB.b = mesh.b;
}
}
};

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



考虑到这一点,只有丹尼尔Daranas的答案似乎是一个解决方案。但我想避免动态转换。



因此,重新设计的问题可能是:如何更改我的设计以确保封装和避免动态转换? >

我使用clang 3.4 ob ubuntu 12.04 lts。

解决方案



以你的方式继承一个基类1目的:暴露一个统一的接口具有不同行为的对象。基本上,你想让子类看起来是一样的。如果我有继承自A的类B和C,我想对类说do foo,它会做 foob fooc



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



所以在你的例子中,你现在在说OK,所以我有一个XML文件,我将从它读取数据一种方式,如果我做一个A ,或另一种方式,如果我做一个B.但是多态方法将是我有一个XML文件,它告诉我做一个A或B,然后我告诉实例解析XML文件。



所以,解决这个问题的方法之一是改变你的求解器接口:

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

虽然这样做会改变问题的方式使用多态性,你创建的,你可能不喜欢这样的原因,我不相信:你必须提供一个默认的构造函数,这将使类处于未知状态。



因此,不是在接口层实施它,你可以在构造函数层执行它,并使SolverA和SolverB必须接受XML字符串作为构造函数的一部分。



但是如果XML字符串是坏的呢?然后你会在构造函数中得到一个错误状态,这也是一个no-no。所以我将使用工厂模式处理这个:

  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,你应该知道你在做什么。



编辑:响应问题



你说的问题是我有一个B和C类型A,如果是B我想设置b设置,如果它是我想要设置c设置。



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



有几种方法可以做到这一点。如果你不介意使用类来处理你的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你必须滚动上午只是为了找到错误。此外,使用多态性的开销是不变的,而反射只是增长和增长和增长。



这样做的最终好处是,如果你有一个 class BB:public B 。你可能有所有的旧设置从B,并想保持他们,只是使它有点大。使用此模型,您可以扩展IO类以及针对BB的io,并重用该代码。


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.

解决方案

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.

这篇关于返回一个Type,或者如何保留一个类型的对象指针?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆