dynamic_cast的正确用例是什么?
What is the proper use case for dynamic_cast?
我多次被告知(并且在实践中亲眼目睹)使用dynamic_cast通常意味着糟糕的设计,因为它可以而且应该用虚函数代替。
例如,考虑以下代码:class Base{...};
class Derived:public Base{...};
...
Base* createSomeObject(); // Might create a Derived object
...
Base* obj = createSomeObject();
if(dynamic_cast<Derived*>(obj)){
// do stuff in one way
}
else{
// do stuff in some other way
}
可以很容易地看到,我们可以将虚函数doStuff()
添加到Base
中,然后在Derived
中重新实现它,而不是编写动态强制转换。
虚函数的问题在于层次结构中的所有类必须有实现或者是抽象的,而这肯定不总是正确的事情。例如,如果Base
是一个接口,并且在if中,您需要访问Derived
的内部实现细节,该怎么办?这在虚拟函数中肯定是不可行的。此外,在某些多重继承情况下,上行和下行都需要dynamic_cast
。在虚函数中可以做的事情是有限制的——例如,模板。最后,有时您需要存储Derived*
,而不仅仅是在其上调用函数。
我认为有两种情况下使用dynamic_cast是有效的。第一个是检查对象是否支持接口,第二个是打破封装。让我详细解释一下。
检查接口
考虑以下函数:
void DoStuffToObject( Object * obj )
{
ITransactionControl * transaction = dynamic_cast<ITransactionControl>( obj );
if( transaction )
transaction->Begin();
obj->DoStuff();
if( transaction )
transaction->Commit();
}
(ITransactionControl将是一个纯抽象类。)在这个函数中,如果对象支持事务语义,我们希望在事务上下文中"DoStuff"。如果没有,也可以继续。
现在我们当然可以在Object类中添加虚拟的Begin()和Commit()方法,但是派生自Object的每个类都会获得Begin()和Commit()方法,即使它们没有事务意识。在这种情况下,在基类中使用虚方法只会污染它的接口。上面的例子促进了对单一职责原则和接口分离原则的更好遵循。
破坏封装
考虑到dynamic_cast通常被认为是有害的,因为允许您破坏封装,这似乎是一个奇怪的建议。然而,如果操作得当,这是一种非常安全且强大的技术。考虑以下函数:std::vector<int> CopyElements( IIterator * iterator )
{
std::vector<int> result;
while( iterator->MoveNext() )
result.push_back( iterator->GetCurrent() );
return result;
}
这里没有什么问题。但是现在假设您开始看到该领域的性能问题。分析之后,您会发现程序在这个函数中花费了大量的时间。push_backs会导致多个内存分配。更糟糕的是,"iterator"几乎总是"ArrayIterator"。如果您能够做出这样的假设,那么您的性能问题就会消失。使用dynamic_cast,您可以做到这一点:
std::vector<int> CopyElements( IIterator * iterator )
{
ArrayIterator * arrayIterator = dynamic_cast<ArrayIterator *>( iterator );
if( arrayIterator ) {
return std::vector<int>( arrayIterator->Begin(), arrayIterator->End() );
} else {
std::vector<int> result;
while( iterator->MoveNext() )
result.push_back( iterator->GetCurrent() );
return result;
}
}
同样,我们可以向IIterator类添加一个虚拟的"CopyElements"方法,但这与我上面提到的缺点相同。也就是说,它使界面膨胀。它强制所有的实现者都有一个CopyElements方法,即使ArrayIterator是唯一会在其中做一些有趣的事情的类。
话虽如此,我还是建议谨慎使用这些技术。Dynamic_cast不是免费的,很容易被滥用。(坦率地说,我看到它被滥用的次数远远多于我看到它被很好地使用的次数。)如果您发现自己经常使用它,那么考虑其他方法是一个好主意。
可以很容易地看到,我们可以在Base中添加一个虚函数doStuff(),然后在Derived中重新实现它,而不是编写动态强制转换。
是的。这就是virtual
函数的作用。
class Base
{
public:
virtual void doStuff();
};
class Derived: public Base
{
public:
virtual void doStuff(); //override base implementation
};
Base* createSomeObject(); // Might create a Derived object
Base* obj = createSomeObject();
obj->doStuff(); //might call Base::doStuff() or Derived::doStuff(), depending on the dynamic type of obj;
你注意到virtual
函数是如何消除dynamic_cast
的吗?
使用dynamic_cast
通常表明您不能使用公共接口(即虚函数)实现您的目标,因此您需要将其强制转换为精确类型,以便调用类型基类/派生类的特定成员函数。
子类可能有基类中没有的其他方法,这些方法在其他子类的上下文中可能没有意义。但一般来说,你应该避免使用它。
如果,你有一个方法(调用它foo),它接收BaseClass*,并为派生类*扩展。如果我写:
BaseClass* x = new DerivedClass();
和调用foo与x,我将得到foo (BaseClass varName),而不是foo (DerivedClass varName)。
一种解决方案是使用dynamic_cast并测试它是否为NULL,如果它不是NULL,则使用转换后的var而不是x调用foo。
这不是最面向对象的情况,但它确实发生了,dynamic_cast可以帮助您解决这个问题(好吧,一般来说,强制转换不太面向对象)。
- 为不同配置设置MSVC_RUNTIME_LIBRARY的正确方法是什么
- C++避免重复声明的语法是什么
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 实现无开销push_back的最佳方法是什么
- C++从另一个类访问公共静态向量的正确方法是什么
- "throw expression code" 1e7 >返回 d 是什么?投掷标准::overflow_error( "too big" ) : d;意味 着?
- C++中名称篡改的目的是什么
- 在 c++ 中拥有一组结构的正确方法是什么?
- 这个指针和内存代码打印是什么?我不知道是打印垃圾还是如何打印我需要的值
- 是什么阻止DOMTimerCoordinator::NextID进入无休止的循环
- 派生类销毁的最佳实践是什么
- 这个语法std::class<>{}(arg1, arg2) 在C++中是什么意思?
- 通过JNI传递数据数组的最快方法是什么
- "using namespace std;"在C++的作用是什么?
- 在两台机器之间进行时间戳的最佳c++chrono函数是什么
- 文件系统:复制功能的速度秘诀是什么
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 使用QQuickFramebufferObject时同步数据的最佳方式是什么
- "Expected '(' for function-style cast or type construction"错误是什么意思?
- 在[expr.static.cast]/4中,术语"一个可行函数"指的是什么