用子类填充的基类向量不起作用
Posted
技术标签:
【中文标题】用子类填充的基类向量不起作用【英文标题】:Base class vector filled with sub-classes not working 【发布时间】:2018-12-19 16:21:53 【问题描述】:我正在尝试拥有一个由具有公共基类的不同子类指针组成的向量。向量设置为基类指针,但添加到向量中的任何内容都无法获得子类的全部功能。 可以在错误日志中看到它被视为基类,因此无法获得扩展功能。
我查看了很多问题,人们说要按照我的方式做,但无论出于何种原因,它都不起作用。
代码在公共 repo.it 上:
https://repl.it/@cubingminer8/inheritance-with-vectors-testing
任何帮助将不胜感激!
编辑:好的,所以我将把它用于 c++ sdl2 游戏引擎中的精灵组系统。将有一个基本精灵类,其中包含一些基本的东西,如渲染和移动,而我需要的任何精灵都将是它们自己的继承自 Sprite 的类,它们将有自己独特的行为,因此虚函数是不切实际的。会有一个精灵组对象,继承自Sprite的对象可以存储在其中。所以它们都可以一次渲染等等。
如果您曾经使用过 pygame,那么它几乎与那里使用的 sprite 和 spritegroup 系统相同。 https://www.pygame.org/docs/tut/SpriteIntro.html
#include <iostream>
#include <vector>
class Base
public:
char A = 'A';
;
class Sub : public Base
public:
char B = 'B';
;
class Sub2 : public Base
public:
char C = 'C';
;
int main()
std::vector<Base*> List;
List.push_back(new Sub());
List.push_back(new Sub2());
std::cout << List[0]->B << std::endl; // it should be able to print B
std::cout << List[1]->C << std::endl; // but it is being set as a base class and
// not getting the functionality of the subclass it is.
【问题讨论】:
人们说要按照我的方式去做唉,他们错了。 在没有丑陋的reinterpret_cast
s 的情况下,很可能安全地做你想做的事情,并且该语言提供了多种工具来做到这一点。一个是virtual
methods。但是,如果您提到如果您告诉我们您想要实现的什么,并且尽可能笼统,那么这个问题会更好地回答。
学习继承的方便补充阅读:The Lyskov Substitution Principle.
【参考方案1】:
通常,这是通过虚函数来实现的。在给定的情况下,它应该是一个虚拟的 getter 函数,它返回每个类的 char
成员。
class Base
char A = 'A';
public:
virtual char getChar()const /*noexcept*/ return A;
virtual Base () = default;
;
class Sub : public Base
char B = 'B';
public:
char getChar()const /*noexcept*/ override return B;
;
class Sub2 : public Base
char C = 'C';
public:
char getChar()const /*noexcept*/ override return C;
;
现在主要
std::cout << List[0]->getChar() << std::endl;
作为旁注,我建议您查看smart pointers,而不是行指针,这样可以避免手动内存管理。
一个好的开始应该是:
#include <memory>
std::vector<std::unique_ptr<Base>> List;
List.emplace_back(std::make_unique<Sub>());
【讨论】:
如果需要,您可以丢弃派生类覆盖上的virtual
s。一旦函数变为虚拟,它就会保持虚拟
并且基类应该有一个虚析构函数。还要确保在完成后删除向量中的对象,因为这不是由向量析构函数执行的,否则会导致内存泄漏。
@axalis 添加了虚拟析构函数;这是一个很好的观点。我对智能指针提出了建议,当然,您的评论应该指导 OP 正确的路径。 ;)【参考方案2】:
所以你想让它工作:
// your code version 1
std::cout<< List[0]->B << std::endl; //it should be able to print B
std::cout<< List[1]->C << std::endl; //but it is being set as a base class
但是如果你改写这个会发生什么?
// your code version 2
std::cout<< List[0]->C << std::endl;
std::cout<< List[1]->B << std::endl;
List[0]
没有任何C
和List[1]
没有任何B
。您打算如何处理这段代码?
有几种方法可以解决这个问题。
-
编译器应该在编译时知道版本 1 是正确的,而版本 2 是错误的。不幸的是,这通常是不可能的,因为编译器无法跟踪哪个对象指针指向数组中的哪个插槽。所以这必须被驳回。
运行时系统应该在运行时检测到错误。这是一种可能的方法,但不是 C++ 采用的方法。 C++ 是一种静态类型语言。动态类型语言可以处理这种情况。如果您想要一种动态类型的语言,请尝试例如蟒蛇。
编译器不应该尝试检测任何东西,运行时系统也不应该尝试检测任何东西,而是继续执行操作,让它产生错误的结果或崩溃。这也是一种可能的方法,但不是任何现代高级编程语言都采用的方法。 C++ 和其他现代语言是typed。可以通过
reinterpret_cast
之类的方式绕过C++的类型系统,但是这样很危险,不推荐。
编译器应将这两个版本都视为错误。这就是 C++ 所做的。
正如其他人所提到的,扩展类功能的(唯一)正确方法是通过虚函数。这需要提前进行一些计划。基类至少应该声明需要哪些 操作,尽管它不需要知道派生类将如何实现它们。
【讨论】:
以上是关于用子类填充的基类向量不起作用的主要内容,如果未能解决你的问题,请参考以下文章
用 DataTable 填充的 SqlDataAdapter 不起作用