通过在其中包含某种ID数据成员来检查对象是否属于特定类型是不是很糟糕?
class A
{
private:
bool isStub;
public:
A(bool isStubVal):isStub(isStubVal){}
bool isStub(){return isStub;}
};
class A1:public A
{
public:
A1():A(false){}
};
class AStub:public A
{
public:
AStub():A(true){}
};
编辑1: 问题是A包含许多虚函数,A1不会覆盖,但是存根需要,因为它表明你正在处理存根而不是实际的对象。这里的可维护性是个问题,对于我添加到A的每个函数,我需要在存根中覆盖它。忘记它意味着危险的行为,因为A的虚函数被存根的数据执行。当然我可以添加一个抽象类ABase,让A和Astub继承它们。但是设计变得足够严格以允许这个重构。 A的引用持有者保存在另一个B类中.B用存根引用初始化,但稍后根据某些条件,B中的引用持有者用A1,A2等重新初始化。所以当我这样做时BObj.GetA (),我可以检查GetA(),如果refholder持有存根,然后在这种情况下给出错误。不执行该检查意味着,我必须使用适当的错误条件覆盖AStub中A的所有功能。
答案 0 :(得分:14)
一般来说,是的。你是一半OO,半程序。
确定对象类型后,您打算做什么?您可能应该将该行为放在对象本身(可能在虚函数中),并且让不同的派生类以不同的方式实现该行为。那么你没有理由检查对象类型。
在您的具体示例中,您有一个“存根”类。而不是......
if(!stub)
{
dosomething;
}
致电
object->DoSomething();
并且AStub中的实现是空的
答案 1 :(得分:3)
一般是的。通常你不想查询对象,但希望它以正确的方式运行。你建议的基本上是原始的RTTI,除非有更好的选择,否则这通常是不受欢迎的。
OO方式是Stub功能,而不是检查它。但是,在“存根”的许多功能的情况下,这似乎不是最佳的。
因此,这取决于你希望班级真正做到什么。
另请注意,在这种情况下,您不会浪费空间:
class A
{
public:
virtual bool isStub() = 0;
};
class A1:public A
{
public:
virtual bool isStub() { return false; };
};
class AStub:public A
{
public:
virtual bool isStub() { return true; };
};
... buuut你有一个虚函数 - 通常不是问题,除非它是性能瓶颈。
答案 2 :(得分:1)
如果要在运行时找到对象的类型,可以使用dynamic_cast。您必须具有指向该对象的指针或引用,然后检查dynamic_cast的结果。如果它不是NULL,则该对象是正确的类型。
答案 3 :(得分:0)
使用多态类,您可以使用typeof
运算符来执行RTTI。大多数时候你不应该这样做。没有多态性,没有语言设施可以做到这一点,但你应该更少需要。
答案 4 :(得分:0)
一个警告。显然你的类型将在施工时确定。如果您确定“类型”是动态数量,则无法使用C ++类型系统解决此问题。在这种情况下,你需要有一些功能。但在这种情况下,最好使用Terry建议的可覆盖/动态行为。
您能提供一些更好的信息,作为您想要完成的工作吗?
答案 5 :(得分:0)
这种事情很好。通常最好将功能放在对象中,这样就不需要打开类型 - 这使得调用代码更简单并且可以对未来的更改进行本地化 - 但是有很多内容可以说是能够检查类型。
一般情况总会有例外情况,即使世界上有最好的意愿,并且能够快速检查奇怪的特定情况可以区分在一个地方通过一个变化固定的东西,快速项目特定代码中特定于项目的黑客攻击,并且必须进行更具侵略性,范围广泛的更改(至少在基类中有额外的功能) - 可能将特定于项目的问题推送到共享或框架代码中。
要快速解决问题,请使用dynamic_cast
。正如其他人所指出的那样,这可以让人们检查某个对象是否属于给定类型 - 或者是从该类型派生的类型(对直接“检查ID”方法的改进)。例如:
bool IsStub( const A &a ) {
return bool( dynamic_cast< const AStub * >( &a ) );
}
这不需要任何设置,如果没有任何努力,结果将是正确的。它也非常简单明了地模板友好。
另外两种方法也适合。
如果派生类型集是固定的,或者有一组常用的派生类型,则可能在基类上有一些将执行强制转换的函数。基类实现返回NULL
:
class A {
virtual AStub *AsStub() { return NULL; }
virtual OtherDerivedClass *AsOtherDerivedClass() { return NULL; }
};
然后适当地覆盖,例如:
class AStub : public A {
AStub *AsStub() { return this; }
};
同样,这允许将派生类型的对象视为它们的基本类型 - 或者不是,如果这是更可取的。这样做的另一个优点是,不必返回this
,但可以返回指向其他对象(可能是成员变量)的指针。这允许给定的派生类提供自身的多个视图,或者可能在运行时更改其角色。
但这种方法并不特别适合模板。这需要一些工作,结果要么更冗长,要么使用不是每个人都熟悉的结构。
另一种方法是重新确定对象类型。有一个表示类型的实际对象,可以通过虚函数和静态函数检索。对于简单类型检查,这并不比dynamic_cast好多少,但是成本在各种编译器中更容易预测,并且存储有用数据的机会(正确的类名,反射信息,可导航的类层次结构信息等)是更大。
这需要一些基础设施(至少是几个宏),以便于添加虚拟功能和维护层次结构数据,但它提供了良好的结果。即使这只用于存储保证有用的类名,并检查类型,它也会收回成本。
完成所有这些后,检查特定类型的对象可能会像这样的例子:
bool IsStub( const A &a ) {
return a.GetObjectType().IsDerivedFrom( AStub::GetClassType() );
}
(IsDerivedFrom
可能是表驱动的,或者它可以简单地循环遍历层次结构数据。这些中的任何一个都可能比dynamic_cast
更有效,但是大约的运行时成本至少是可预测的。)
与dynamic_cast一样,这种方法显然也适用于使用模板进行自动化。
答案 6 :(得分:0)
在一般情况下,它可能不是一个好的设计,但在某些特定情况下,为使用特定客户端提供isStub()方法是合理的设计选择,否则需要使用RTTI。一个这样的情况是延迟加载:
class LoadingProxy : IInterface
{
private:
IInterface m_delegate;
IInterface loadDelegate();
public:
LoadingProxy(IInterface delegate) : m_delegate(delegate){}
int useMe()
{
if (m_delegate.isStub())
{
m_delegate = loadDelegate();
}
return m_delegate.useMe();
}
};
与虚拟方法调用相比,RTTI的问题在于它相对昂贵(慢),因此如果您的useMe()函数简单/快速,RTTI将确定性能。在我使用的一个应用程序上,使用RTTI测试来确定是否需要延迟加载是分析所确定的性能瓶颈之一。
但是,正如许多其他答案所述,应用程序代码不应该担心它是否有存根或可用实例。测试应位于应用程序的一个位置/层中。除非您可能需要多个LoadingProxy实现,否则可能会使isStub()成为友元函数。