识别派生类类型的正确方法(类型实体VS dynamic_case)

时间:2015-11-21 19:23:23

标签: c++ inheritance dynamic-cast typechecking

我试图实现从一个基类派生的数据类。

每个派生类都有不同的数据字段

因此,每个实例都必须根据数据实例的类型进行不同的处理。

我为此制作了一个示例代码。

#include <iostream>
#include <algorithm>
#include <boost/shared_ptr.hpp>
using namespace std;

enum DataTypes{
    EMPTY = 0,
    TYPE1,
    TYPE2
};
class DataBase {
public:
    DataBase():type(EMPTY){}
    virtual ~DataBase(){}
    DataTypes getType() const{return type;}

protected:
    DataBase(DataTypes n):type(n){}
    DataTypes type;
};

class DataType1 :public DataBase {
public:
    DataType1(): DataBase(TYPE1){}

    string data_for_class1;
};

class DataType2 :public DataBase {
public:
    DataType2(): DataBase(TYPE2){}

    string data_for_class2;
};

boost::shared_ptr<DataBase> createInstance(int n){
    boost::shared_ptr<DataBase> p;
    if(n == 1){
        p.reset(new DataType1);
        boost::shared_ptr<DataType1> temp = boost::static_pointer_cast<DataType1>(p);
        temp->data_for_class1 = "[Data for DataType1]";
    }
    else if(n==2){
        p.reset(new DataType2);
        boost::shared_ptr<DataType2> temp = boost::static_pointer_cast<DataType2>(p);
        temp->data_for_class2 = "[Data for DataType2]";
    }

    return p;
}

int main() {
    boost::shared_ptr<DataBase> p = createInstance(2);
    try{
        /*
        if p is an instance of DataType1
            process p as DataType1;
        else if p is an instance of DataType2
            process p as DataType2;
        else
            throw exception("Empty data");
        */
    }catch(runtime_error& e){
        cerr<<e.what()<<endl;
    }

    return 0;
}

应填写try-catch表达式中的注释部分。

在努力思考之后,我得到了两个可能的解决方案。

1。检查type

中的DataBase字段

为此,

  • DataBase课程应该有type字段。
  • DataBase派生的每个类都负责使用适当的参数调用构造函数(DataBase(DataTypes n))。

代码:

switch (p->getType()){
    case TYPE1:
    {
        cout<<"This is an instance of DataType1"<<endl;
        boost::shared_ptr<DataType1> temp = boost::static_pointer_cast<DataType1>(p);
        cout<<temp->data_for_class1<<endl;
    }
        break;
    case TYPE2:
    {
        cout<<"This is an instance of DataType2"<<endl;
        boost::shared_ptr<DataType2> temp = boost::static_pointer_cast<DataType2>(p);
        cout<<temp->data_for_class2<<endl;
    }
        break;
    case EMPTY: default:
        throw runtime_error("Data is empty");
}

2。对所有派生类使用dynamic_cast

与case1相比,这种方法

  • 不需要DataBase具有用于类型检查的附加字段。
  • 但我不知道dynamic_cast是做这件事的好方法。

代码:

if(boost::dynamic_pointer_cast<DataType1>(p)){
    cout<<"This is an instance of DataType1"<<endl;
    boost::shared_ptr<DataType1> temp = boost::static_pointer_cast<DataType1>(p);
    cout<<temp->data_for_class1<<endl;
}
else if(boost::dynamic_pointer_cast<DataType2>(p)){
    cout<<"This is an instance of DataType2"<<endl;
    boost::shared_ptr<DataType2> temp = boost::static_pointer_cast<DataType2>(p);
    cout<<temp->data_for_class2<<endl;
}
else{
    throw runtime_error("Data is empty");
}

以上两种方式,我都可以获得工作代码。但我不确定这是否是正确的做法。

如果上述代码存在潜在问题,或者您知道更好的解决方案,请分享您的好主意。

感谢。

4 个答案:

答案 0 :(得分:0)

如果您使用的是polymorphishm,那么最好利用提供的语言功能来支持。因此#2优于#1

您可能还想避免使用dynamic_cast并使用语言提供的运行时类型检查 - &#34; typeid(* p).name&#34;。这将为您提供由&#34; p&#34;。

表示的对象

使用这种方法重写您的示例:

if(typeid(*p).name == "DataType1"){
    cout<<"This is an instance of DataType1"<<endl;
    boost::shared_ptr<DataType1> temp = boost::static_pointer_cast<DataType1>(p);
    cout<<temp->data_for_class1<<endl;
}
else if(typeid(*p).name == "DataType2"){
    cout<<"This is an instance of DataType2"<<endl;
    boost::shared_ptr<DataType2> temp = boost::static_pointer_cast<DataType2>(p);
    cout<<temp->data_for_class2<<endl;
}
else{
    throw runtime_error("Data is empty");
}

答案 1 :(得分:0)

还有解决方案#3,其中存储了虚拟getType(并且没有type字段。

class DataBase {
public:
    DataBase() {}
    virtual ~DataBase() {}

    virtual DataTypes getType() { return EMPTY; } // or '= 0;'
};

class DataType1 :public DataBase {
public:
    //...
    DataTypes getType() { return TYPE1; };
};

class DataType2 :public DataBase {
public:
    //...
    DataTypes getType() { return TYPE2; };
};

答案 2 :(得分:0)

方式1更有效,因为它不使用运行时类型信息。

答案 3 :(得分:0)

给出的所有选项都不是很好的设计,因为它们似乎处理数据库,但实际上只适用于DataBase的子类型,这些子类型也在一些特殊的受支持类型列表中。演员们放弃了。更正式地说,这违反了Liskov替代原则。

因为该类被称为&#34; DataBase&#34;,我猜你不想在DataBase中放入一个纯粹的虚拟进程(...)方法,即子类将实施

在这种情况下,您应该使用双重发送:https://en.wikipedia.org/wiki/Double_dispatch

例如,您可以像这样创建一个DBConsumer接口:

class DBConsumer
{
    public:
    virtual void accept(DataType1 &data) = 0;
    virtual void accept(DataType2 &data) = 0;
}

然后,在您的基类中:

class DataBase
{
    ...
    virtual void sendTo(DBConsumer &consumer) = 0;
    ...
}

并在每个子类中实现,以调用适当的重载:

class DataType1
{
    ...
    void sendTo(DBConsumer &consumer)
    {
        consumer.accept(*this); //calls DataType1 overload
    }
    ...
}
class DataType2
{
    ...
    void sendTo(DBConsumer &consumer)
    {
        consumer.accept(*this); //calls DataType2 overload
    }
    ...
}

现在,如果要处理DataTypes,可以实现为每个子类实现accept()重载的使用者:

int main()
{
    ...
    MyDBConsumer myConsumer;
    boost::shared_ptr<DataBase> p = createInstance(2);
    p->sendTo(myConsumer);
}

多田!没有强制转换,也没有支持类型的魔术列表。当有人添加新的DataType3子类时,编译器将确保所有DBConsumers 支持它。