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

Proper way to identify type of derived class(type entity VS dynamic_case)

我正在尝试实现从一个碱基 class 派生的数据 classes。

并且每个派生的 classes 都有 不同的数据字段

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

我为此编写了一个示例代码。

#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。检查 DataBase class

中的 type 字段

为此,

代码:

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。对所有派生的 classes

使用 dynamic_cast

与案例 1 相比,此方法

代码:

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

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

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

谢谢。

如果你正在使用多态,那么最好利用提供的语言特性来支持。因此#2 优于#1

您可能还想避免 dynamic_cast 并使用 运行 语言提供的时间类型检查 - "typeid(*p).name"。那会给你 "p".

代表的对象

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

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

还有一个解决方案 #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; };
};

方式 1 更有效,因为它不使用 运行-time-type-in​​formation。

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

因为 class 被称为 "DataBase",我猜你不想只在数据库中放置一个纯虚拟进程(...)方法,subclasses 将实施。

在那种情况下,你应该使用双分派:https://en.wikipedia.org/wiki/Double_dispatch

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

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

然后,在你的基地class:

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

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

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

现在,如果你想处理数据类型,你实现一个消费者,为每个子class:

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

多田!没有转换,也没有受支持类型的魔法列表。当有人添加新的 DataType3 subclass 时,编译器将确保所有 DBConsumers.

都支持它