【问题标题】:C++: Cast parent class to child classC ++:将父类转换为子类
【发布时间】:2013-01-23 23:40:13
【问题描述】:

我对 C++ 还很陌生,这是我遇到的问题: 我有两个班级,ClientHost。加载所有内容后,您可以选择按两个按钮,如果您按按钮 1 Client 已加载,如果您按按钮 2 Host 已加载。

现在ClientHost 都是相当大的类,我不想将它们都放入内存中。所以我的想法是创建一个Base 类,然后ClientHost 都应该扩展基类,然后我唯一要做的就是:

Base connection;

//If button 1 is pressed:
connection = Client();

//If button 2 is pressed:
connection = Host();

这听起来好得令人难以置信,当我尝试它时,我没有发现任何错误。现在问题来了,Base 有一个名为A 的函数,而Client 有一个名为B 的函数。所以函数B是类Client所独有的。

当我尝试调用函数 B 时,我收到此错误:'class Base' has no member named 'B'。如何让 C++ 知道我正在与 ClientHost 类而不是 Base 交谈?我也愿意采用全新的方法来解决这个问题。也许这只是我思考过程中的一个错误。

提前致谢!

【问题讨论】:

  • 对象切片相关的问题最近好像很流行。
  • 您的代码可能没有按照您的意愿行事。查找对象切片
  • 您分别实例化客户端或主机。但是,您将它们分配给 Base 变量,该变量将它们转换(实际上它复制)到新的 Base 实例,因此原始身份丢失,也称为作为对象切片。
  • 我已经快速阅读了有关对象切片的内容,但有没有一种方法可以在两个类之间选择运行时而不将它们都放在内存中?
  • AB 这些不同的功能到底是什么?可能有更基本的行为需要抽象。

标签: c++ oop class inheritance casting


【解决方案1】:

您遇到了我们称之为对象切片的情况,这是具有值语义的语言(例如C++)的常见问题。

当您将子类型的值分配给超类型的新位置(您的变量connection)时,就会发生对象切片。这引入了实例的一个新副本,但它是超类型,而不是子类型,因此您会丢失有关要实例化的具体类的信息。

为避免这种情况,您有多种选择。

经典方法使用指针

Base * connection;
connection = new YourConcreteType();

然后,要使用此对象,您必须使用星号运算符 (*)取消引用

(*connection).function();
connection->function();    // syntactic sugar, semantically equivalent

不要忘记:你必须在使用后删除对象:

delete connection;

为了简化这一点,C++ 引入了两个概念:引用智能指针。虽然前者有一个限制,只能分配一次,但它在语法上是最简单的。后者类似于指针的方式,但你不必关心删除,所以你不太可能遇到内存泄漏的情况:

std::shared_ptr<Base> connection;

connection = make_shared<YourConcreteType>(); // construction via 'make_shared'

// ... use as if it were just a pointer ...

connection->function();

// no delete!

还有其他“智能指针”类型,例如unique_ptr,如果您不打算传递指针(如果它保持在范围内),可以使用它们。

现在,您可以分别实现两个类中的功能。为了利用多态性,这意味着,在运行时,要么调用一个子类的函数,要么调用另一个子类的函数,这取决于构造了哪一个,你应该将基类中的函数声明为virtual,否则,Base 中的函数定义将被调用,无论你构造的具体类型如何。

在您的情况下,您想要调用一个应该做不同事情的函数,具体取决于类型。虽然您的方法是引入两个不同的函数,即AB,但您可以只声明一个函数,我们称之为handleEvent,作为一个纯虚拟(=抽象)函数在基类中,意思是“这个功能要在子类中实现”,在两个子类中独立定义:

Base {
    ....
    virtual void handleEvent(...) = 0; // "= 0" means "pure"
};

// Don't provide an implementation

Client {
    void handleEvent(...); // override it
};

// define it for Client:
void Client::handleEvent(...) {
    ...
}

Host {
    void handleEvent(...); // override it
};

// define it for Host:
void Host::handleEvent(...) {
    ...
}

【讨论】:

  • unique_ptr 应该是默认的智能指针。完全不清楚是否需要共享所有权。
  • 非常感谢您的精彩解释,我想我明白您的意思了。因此,如果我做对了,我只需在Base 类中为BC 创建一种“原型”,然后在ClientHost 中对其进行编辑。之后,我将Base 类称为指针,以便可以对其进行更改,但是由于函数已经在Base 类中使用“原型”函数进行了初始化,因此我只需要引用内存即可。我说的对吗?
  • @juanchopanza 嗯,有一些问题,我认为共享指针通常更易于使用,尤其是对于新手。当然,值得一提的是,唯一指针可以在很多情况下使用(但不是全部,例如在容器中)并且它们更有效。
  • @leemes 哦,是的,我绝对有兴趣了解 C++ 编程的来龙去脉,这就是为什么我对这次讨论如此高兴:)
  • @ThomasVersteeg Rarity 在 Stack Overflow 上,遗憾的是。好吧,然后buy a book 并享受学习 C++ 的乐趣!祝你好运;)
【解决方案2】:

当你这样做时:

Base connection;

您创建了一个对象connection,该对象恰好具有Base 类型所需的内存。

当你这样做时:

connection = Client();

您没有扩展分配的内存或将connection 转换为Client 实例。相反,您正在创建一个更大的对象,然后将其“切片”成一个较小的对象。

您想要做的是使用指针(或引用或智能指针),以便您保留的只是可能是一种类型或另一种类型的对象的地址。 像这样:

Base *connection;
...
connection = new Client();

第一个语句创建一个指向Base 类型对象的指针,第二个语句为Client 类型对象分配内存,对其进行初始化并将其地址分配给connection

【讨论】:

  • 当我做你所做的,并像这样称呼它connection-&gt;A() 我仍然得到'class Base' has no member named 'sendInput' 错误!
  • 通常当您从基类派生类时,是因为它们共享一个公共接口。因此,您可能希望将A() 声明为Base 上的虚函数,或使用动态转换(不建议)。 dynamic_cast&lt;Client*&gt;(connection)-&gt;A().
【解决方案3】:

首先,这一行

connection = Client();

正在使用赋值运算符从临时Client 对象设置connection 的状态,Baseconnection 仍然是 Base 对象。您可以执行以下操作:

std::unique_ptr<Base> makeBase(someFlagType flag)
{
  if (flag) {
    return std::unique_ptr<Base>(new Cient);
  } else {
    return std::unique_ptr<Base>(new Host);
  }
}

然后

std::unique_ptr<Base> b = makeBase(myFlag);
b->someBaseMethod();

关于转换部分,我想说,如果您发现自己必须转换为子类型,您应该重新考虑类的设计。

【讨论】:

  • 是的,我同意重新思考的部分,但你建议我如何在不使用不必要内存的情况下做到这一点?
  • @ThomasVersteeg 有一个处理Client 的代码分支和一个处理Host 的代码。
  • 对不起,我不太明白你的意思。
  • @ThomasVersteeg 这真的取决于您的应用程序的细节。没有“一刀切”的解决方案。
【解决方案4】:

如果您仍然想使用 Base,那么您将不得不这样做才能使用这些功能:

if (Button1) {
    dynamic_cast<Client*>(connection)->A();
} else {
    dynamic_cast<Host*>(connection)->B();   
}  

您需要将连接设为指针。 Base * connection.

但这并不理想。您应该像其他答案一样研究一种不同的方法。

【讨论】:

  • dynamic_casts 必须使用 -> 语法(它们解析为指针),并且您需要处理连接不是适当类型的情况(其中 dynamic_cast 返回空指针)。如果您绝对确定您的类型是“客户端”(当 Button1 是 trie 时)和“主机”,否则,您可以使用 static_cast 向下转换为适当的类型。然而;这种方法通常是应该避免的,并且通常表示可以使用上述方法修复的设计问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-18
  • 2013-05-08
相关资源
最近更新 更多