Dynamic casting
显式类型转换(转换),我们了解了转换的概念,并使用static_cast将变量从一种类型转换为另一种类型。
在本课中,我们将继续学习另一种类型的强制转换:dynamic_cast。
dynamic_cast
处理多态时,您经常会遇到指向基类的情况,但是您希望访问仅存在于派生类中的某些信息。
考虑以下(略设的)程序:
#include <iostream>
#include <string>
class Base
{
protected:
int m_value;
public:
Base(int value)
: m_value(value)
{
}
virtual ~Base() {}
};
class Derived : public Base
{
protected:
std::string m_name;
public:
Derived(int value, std::string name)
: Base(value), m_name(name)
{
}
const std::string& getName() { return m_name; }
};
Base* getObject(bool bReturnDerived)
{
if (bReturnDerived)
return new Derived(1, "Apple");
else
return new Base(2);
}
int main()
{
Base *b = getObject(true);
//只有一个Base指针,我们如何在这里打印Derived对象的名称?
delete b;
return 0;
}
在此程序中,函数getObject()始终返回Base指针,但该指针可能指向Base或Derived对象。在指针指向Derived对象的情况下,我们如何调用Derived :: getName()?
一种方法是向Base添加一个名为getName()的虚函数(因此我们可以使用Base对象调用它,并将其动态解析为Derived :: getName())。但是,如果用Base对象调用它,该函数会返回什么?实际上并没有任何有意义的价值。此外,我们将使用真正应该只关注Derived类的事物来污染我们的Base类。
我们知道C ++会隐式地允许你将Derived指针转换为Base指针(事实上,getObject()就是这样)。此过程有时称为向上转换。但是,如果有一种方法将Base指针转换回Derived指针怎么办?然后我们可以直接使用该指针调用Derived :: getName(),而不必担心虚函数解析。
dynamic_cast
C ++提供了一个名为dynamic_cast的转换操作符,可用于此目的。尽管动态转换具有一些不同的功能,但到目前为止,动态转换的最常见用途是将基类指针转换为派生类指针。此过程称为向下转换。
使用dynamic_cast就像static_cast一样。这是我们上面的示例main(),使用dynamic_cast将Base指针转换回Derived指针:
int main()
{
Base *b = getObject(true);
Derived *d = dynamic_cast<Derived*>(b); //使用动态强制转换将Base指针转换为Derived指针
std::cout << "The name of the Derived is: " << d->getName() << '\n';
delete b;
return 0;
}
这打印:
The name of the Derived is::Apple
dynamic_cast失败
上面的示例有效,因为b实际上指向Derived对象,因此将b转换为Derived指针是成功的。
但是,我们做了一个非常危险的假设:b指向Derived对象。如果b没有指向Derived对象怎么办?通过将getObject()的参数从true更改为false,可以轻松地对此进行测试。在这种情况下,getObject()将返回一个Base对象的Base指针。当我们尝试将其动态广播到Derived时,它将失败,因为无法进行转换。
如果dynamic_cast失败,则转换结果将为空指针。
因为我们没有检查空指针结果,所以我们访问d-> getName(),它将尝试取消引用空指针,从而导致未定义的行为(可能是崩溃)。
为了使这个程序安全,我们需要确保dynamic_cast的结果实际成功:
int main()
{
Base *b = getObject(true);
Derived *d = dynamic_cast<Derived*>(b); // 使用动态强制转换将Base指针转换为Derived指针
if (d) // 确保d为非null
std::cout << "The name of the Derived is: " << d->getName() << '\n';
delete b;
return 0;
}
规则:通过检查空指针结果,始终确保动态强制转换成功。
请注意,因为dynamic_cast在运行时进行了一些一致性检查(以确保可以进行转换),所以使用dynamic_cast确实会导致性能下降。
另请注意,在某些情况下,使用dynamic_cast进行的向下转换不起作用:
1)具有受保护或私有继承。
2)对于未声明或继承任何虚函数的类(因此没有虚拟表)。
3)在某些涉及虚拟基类的情况下(请参阅此页面了解其中一些案例的示例,以及如何解决它们)。
使用static_cast进行向下转换
事实证明,向下转换也可以使用static_cast完成。主要区别在于static_cast没有运行时类型检查,以确保您正在做的事情有意义。这使得使用static_cast更快,但更危险。如果将Base *转换为Derived *,即使Base指针未指向Derived对象,它也将“成功”。当您尝试访问生成的Derived指针(实际上指向Base对象)时,这将导致未定义的行为。
如果您完全确定您正在向下转换的指针将成功,那么使用static_cast是可以接受的。确保您知道所指向的对象类型的一种方法是使用虚函数。这是一个(不是很好,因为它使用全局变量)方法来做到这一点:
#include <iostream>
#include <string>
// 类标识符
enum ClassID
{
BASE,
DERIVED
// 其他的可以在这里添加
};
class Base
{
protected:
int m_value;
public:
Base(int value)
: m_value(value)
{
}
virtual ~Base() {}
virtual ClassID getClassID() { return BASE; }
};
class Derived : public Base
{
protected:
std::string m_name;
public:
Derived(int value, std::string name)
: Base(value), m_name(name)
{
}
std::string& getName() { return m_name; }
virtual ClassID getClassID() { return DERIVED; }
};
Base* getObject(bool bReturnDerived)
{
if (bReturnDerived)
return new Derived(1, "Apple");
else
return new Base(2);
}
int main()
{
Base *b = getObject(true);
if (b->getClassID() == DERIVED)
{
// 我们已经证明b指向Derived对象,所以这应该总是成功的
Derived *d = static_cast<Derived*>(b);
std::cout << "The name of the Derived is: " << d->getName() << '\n';
}
delete b;
return 0;
}
但是如果你要经历所有麻烦来实现它(并支付调用虚函数和处理结果的成本),你也可以使用dynamic_cast。
dynamic_cast和引用
虽然以上所有示例都显示了指针的动态转换(更常见),但dynamic_cast也可以与引用一起使用。这类似于dynamic_cast如何使用指针。
#include <iostream>
#include <string>
class Base
{
protected:
int m_value;
public:
Base(int value)
: m_value(value)
{
}
virtual ~Base() {}
};
class Derived : public Base
{
protected:
std::string m_name;
public:
Derived(int value, std::string name)
: Base(value), m_name(name)
{
}
const std::string& getName() { return m_name; }
};
int main()
{
Derived apple(1, "Apple"); // 创造一个apple
Base &b = apple; // 设置对象的基本引用
Derived &d = dynamic_cast<Derived&>(b); // 使用引用而不是指针进行动态转换
std::cout << "The name of the Derived is: " << d.getName() << '\n'; // 我们可以通过d访问Derived :: getName
return 0;
}
因为C ++没有“空引用”,所以dynamic_cast在失败时不能返回空引用。相反,如果引用的dynamic_cast失败,则抛出类型为std :: bad_cast的异常。我们将在本教程后面讨论异常。
dynamic_cast vs static_cast
新程序员有时会对使用static_cast和dynamic_cast感到困惑。答案很简单:除非你是向下转换,否则使用static_cast,在这种情况下,dynamic_cast通常是更好的选择。但是,您还应该考虑完全避免使用虚拟函数。
向下转型与虚拟函数
有些开发人员认为dynamic_cast是糟糕的,并且表明糟糕的类设计。相反,这些程序员说你应该使用虚函数。
通常,使用虚函数应优先于向下转换。但是,有时候向下转型是更好的选择:
#当您无法修改基类以添加虚函数时(例如,因为基类是标准库的一部分)
#当您需要访问特定于派生类的内容时(例如,仅存在于派生类中的访问函数)
#向基类添加虚函数时没有意义(例如,返回的基类没有适当的值)。如果您不需要实例化基类,则可以使用纯虚函数。