C++基础教程面向对象(学习笔记(62))

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是糟糕的,并且表明糟糕的类设计。相反,这些程序员说你应该使用虚函数。

通常,使用虚函数应优先于向下转换。但是,有时候向下转型是更好的选择:

#当您无法修改基类以添加虚函数时(例如,因为基类是标准库的一部分)
#当您需要访问特定于派生类的内容时(例如,仅存在于派生类中的访问函数)
#向基类添加虚函数时没有意义(例如,返回的基类没有适当的值)。如果您不需要实例化基类,则可以使用纯虚函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值