如何在C++从父实例调用子函数

How to downcast in C++ to call a child function from a parent instance?

本文关键字:实例 调用 子函数 C++      更新时间:2023-10-16

我正在尝试使用显式向下转换从父实例调用子函数(感谢您指出@Aconcagua)。作为C++的初学者,我有这样的东西:

Road currentRoad = ...;
duration = ((SpeedDataRoad) currentRoad).getSpeedProfileTime(dateinMillis, isRightDirection);

SpeedDataRoad继承自Road

class SpeedDataRoad : public Road{ 
double getSpeedProfileTime(long dateinMillis, bool isRightDirection) {
...
}

但是我收到错误:

C 样式转换从"道路"到"速度数据道路"的转换不匹配

任何关于我做错了什么的建议将不胜感激。


需要明确的是,我试图在 Java 中实现的目标将像这样编写并正常工作:

duration = ((SpeedDataRoad) currentRoad).getSpeedProfileTime(currentTime, isRightDirection);

您遭受一种称为"对象切片">的效果:

SpeedDataRoad sdr;
Road currentRoad = sdr;

在第二行,sdr分配给currentRoad,但后者的类型不适合保存完整的SpeedDataRoad对象。因此,所有多余的SpeedDataRoad都被简单地切掉,剩下的只是一个纯粹的Road对象,只包含原始sdr对象的Road部分。

同时,由于您只剩下一个纯Road对象,因此无法将其转换回SpeedDataRoad对象。现在缺失的零件应该从哪里来?

这与不能将多态类型直接放入基类的容器(如std::vector)的原因完全相同。

你需要的是指针(如果你想能够重新分配)或引用(否则优先):

SpeedDataRoad sdr;
Road& currentRoad = sdr;
//  ^ (!)
// or:
Road* currentRoad = &sdr;

现在你可以做演员表了。但露骨的下投有一股糟糕设计的味道。从一开始就使用多态方法可能会更好:

class Road
{
public:
virtual double getSpeedProfileTime(long, bool) = 0;
//                                             ^ pure virtual
// alternatively, you can provide a default implementation
};
class SpeedDataRoad : public Road
{
public:
double getSpeedProfileTime(long, bool) override
{ /* ... */ }
};

现在,您可以简单地拥有:

SpeedDataRoad sdr;
Road& currentRoad = sdr;
double profile = currentRoad.getSpeedProfileTime(0, false);

作为虚拟的,你总是会得到函数的正确变体,无论我们拥有哪个子类以及它可能以何种方式覆盖函数......

旁注 1:您可能更喜欢更现代的 C++C 样式转换,而不是旧的 C 样式转换,您可以控制更细粒度的您实际想要做的事情:

Road* someRoad = ...;
SpeedDataRoad* sdr = static_cast<SpeedDataRoad*>(someRoad);
SpeedDataRoad* sdr = dynamic_cast<SpeedDataRoad*>(someRoad);

如果您 100% 确定对象只能是所需类型,则可以使用static_cast。在这种情况下,您可以避免任何根本无法提供任何服务的运行时测试(无论如何,您都是 100% 确定的,还记得吗?奇怪的重复出现的模板模式是典型的方案。

如果您无法确定类型,那么dynamic_cast发挥作用,它将执行一些运行时类型检查,如果实际类型不是所需的类型(或子类),则只返回一个空指针(如果用于指针)或抛出一个std::bad_cast(如果用于引用)。当不同的多态类型存储在向量中时,可能会出现这种情况(作为基类的指针,见上文)。但再说一遍:需要演员阵容可能暗示你的设计存在缺陷......

(为了完整起见:还有const_castreinterpret_cast,但你应该远离这些,除非/直到你真的知道你在做什么。

旁注2:与Java的差异。

在 Java 中,我们隐式区分本机类型和引用类型。本机的总是按值传递,引用类型总是通过引用传递——嗯,Java引用,它实际上更像一个C++指针(可以null,可以重新分配),而不是C++引用。在 Java 中,这是隐式发生的,在 C++ 中,您需要明确(另一方面,对于任何类型的行为,您可以同时具有这两种行为)。

Javacast on (Java!) 引用的行为类似于 C++dynamic_cast(在引用时,即抛出,它不会在类型不匹配时返回null)。

最后(关于我的多态性建议),在Java中,所有函数都是隐式虚拟的,在C++中,你必须再次明确(应用virtual关键字,见上文)。

您正在切割SpeedDataRoad对象。 与具有指针/引用语义的 Java 对象不同,C++对象具有值语义。 这意味着,在您的示例中,currentRoadRoad,而不是SpeedDataRoad。 它是在您的...中创建的任何SppedDataRoadRoad部分的副本。

要使用多态性C++需要使用引用或指针。 也就是说,以下内容将不起作用,因为currentRoad不是SpeedDataRoad

double foo(Road currentRoad)
{
//...
return ((SpeedDataRoad)currentRoad).getSpeedProfileTime(currentTime, isRightDirection);
}
int main()
{
SpeedDataRoad road;
foo(road);
}

虽然以下内容起作用,因为currentRoad引用SpeedDataRoad

double foo(Road& currentRoad)
//             ^---------------- Pass by reference now
{
//...
return dynamic_cast<SpeedDataRoad&>(currentRoad).getSpeedProfileTime(currentTime, isRightDirection);
//     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//     Using C++-style cast and casting reference to reference
}
int main()
{
SpeedDataRoad road;
foo(road);
}

在第一个示例中,currentRoadroadRoad部分的副本,而在第二个示例中,currentRoad是对road引用


您还应该避免在C++中使用 C 型转换。 在这种情况下,最好使用dynamic_cast<SpeedDataRoad&>(currentRoad),或者,如果您绝对确定currentRoad将始终是对SpeedDataRoad对象的引用,则static_cast<SpeedDataRoad&>(currentRoad)。 前者将执行运行时类型检查,如果currentRoad不是对SpeedDataRoad的引用,则引发异常,而后者将避免执行运行时类型检查的(小)开销,但如果currentRoad不是对SpeedDataRoad的引用,则会导致未定义的行为。

这被称为向下投射而不是向上投射 -dynamic_cast的直接方法:

if (SpeedDataRoad* sdroad = dynamic_cast<SpeedDataRoad*>(&currentRoad); sdroad != nullptr) {
duration = sdroad->getSpeedProfileTime(currentTime, isRightDirection);
}

如果要在函数内部检查是否可以将指针/引用向下转换为子项,则需要使用动态强制转换。

void foo(Road* road){
SpeedDataRoad* child{nullptr};
if(child = dynamic_cast<SpeedDaraRoad*>(road){
// Do something with SpeedDataRoad
} else {
// road is not an instance of SpeedDataRoad
}
}

您还可以将dynamic_cast与引用一起使用,例如

cppSpeedDataRoad& child = dynamic_cast<SpeedDataRoad&>(reference_to_road);

但要小心,好像如果演员阵容失败,std::bad_cast就会被扔掉。

在C++中,我们尽量不使用 C 样式转换。(typename)object. 相反,有 4 种类型的类型转换。

  1. static_cast<typename*>(pointer) static_cast<typename>(value):指针的向上转换和值类型的类型转换

  2. dynamic_cast<typename*>(pointer):指针的安全向下转换(您应该使用的指针)。它对上行转换进行运行时检查,因此存在运行时成本。

  3. const_cast<...>(...): 常量型

  4. reinterpret_cast<typename*>(pointer) reinterpret_cast<typename>(value): 类似于C型演员表。

对于对象(堆栈分配),我很少使用强制转换。由于C++中的自然物体有自己的尺寸,因此铸造可能会改变其尺寸。