识别派生类型的正确方法 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
字段
为此,
DataBase
class 应该有 type
字段。
- 从
DataBase
派生的每个 classes 负责调用具有适当参数的构造函数 (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。对所有派生的 classes
使用 dynamic_cast
与案例 1 相比,此方法
- 不要求
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");
}
以上两种方式,我都可以获得工作代码。但我不确定这样做是否正确。
如果以上代码有潜在问题,或者您有更好的解决方案,请分享您的好主意。
谢谢。
如果你正在使用多态,那么最好利用提供的语言特性来支持。因此#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-information。
所有给出的选项都不是很好的设计,因为它们似乎处理数据库,但实际上只适用于数据库的子类型,这些子类型也在一些特殊的支持类型列表中。演员们放弃了。更正式地说,这违反了 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.
都支持它
我正在尝试实现从一个碱基 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
字段
为此,
DataBase
class 应该有type
字段。- 从
DataBase
派生的每个 classes 负责调用具有适当参数的构造函数 (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。对所有派生的 classes
使用dynamic_cast
与案例 1 相比,此方法
- 不要求
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");
}
以上两种方式,我都可以获得工作代码。但我不确定这样做是否正确。
如果以上代码有潜在问题,或者您有更好的解决方案,请分享您的好主意。
谢谢。
如果你正在使用多态,那么最好利用提供的语言特性来支持。因此#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-information。
所有给出的选项都不是很好的设计,因为它们似乎处理数据库,但实际上只适用于数据库的子类型,这些子类型也在一些特殊的支持类型列表中。演员们放弃了。更正式地说,这违反了 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.
都支持它