【问题标题】:C++ Upcasting from Multiple Inheritance; the source is not polymorphicC++ 从多重继承向上转型;源不是多态的
【发布时间】:2019-10-14 21:11:57
【问题描述】:

我是 C++ 的新手。 我有两个纯抽象类(如接口),我从这两个纯抽象类派生一个类。

在某种情况下,我需要将派生类指针向上转换为基础抽象类之一。 首先,有没有限制。

class IBase1
{
  virtual ~IBase() = default;
}

class IBase2
{
  virtual ~IBase2() = default;
}

class Derived : public IBase, public IBase2
{
}

Derived d;
IBase1* basePtr = dynamic_cast<IBase1*>(&d);

在这里,如果我使用任何其他类型转换,我不太确定,但我从类型转换中得到了无效的指针,所以我需要使用 dynamic_cast 从多重继承中向上转换,对吗?

当我这样做时,我得到一个错误“源类型不是多态的”

我的基类是纯抽象类,所以它们至少有一个虚拟方法,所以应该没问题,但是为什么会出现这个错误?是关于多重继承吗?

编辑:这里还有一层。 我的派生类需要包含两种不同类型的实例,但我的实例确实是巨大的变量,因此作为 C 开发人员:),我计划使用 Union 来减少内存使用量。 联合只有从纯抽象类派生的两个类的实例。所以,我假设联合实例地址也应该指向我的类实例的偏移量,但是 C++ 不可能知道写入成员方法的地址。

class IFile
{
public:
  virtual ~IFile() = default;
};

class IDirectory
{
public:
  virtual ~IDirectory() = default;
};

class FileSystem1 : public IFile, public IDirectory
{
public:
  FileSystem1() { }

  virtual ~FileSystem1() final override = default;
private:
   Native1APIInstance file;
};

class FileSystem2 : public IFile, public IDirectory
{
public:
  FileSystem2() { }

  virtual ~FileSystem2() final override = default;
private:
   Native2APIInstance file;
};

union FileSystemInstance
{
  FileSystem1 fs1;
  FileSystem2 fs2;
  FileSystemInstance(string path)
  {
    if (path[0] == '1') // initialise fs1
    else if (path[0] == '2') // initialise fs2
  }
};

FileSystem fs("<PATH to File System>");
IFile* file = reinterpret_cast<IFile*>(&fs);

在这里,我不想关心初始化了哪个实例。我只想使用基类接口。我想知道联盟是否有可能?

谢谢。

【问题讨论】:

  • 修复代码,让它显示问题,而不是一堆拼写错误。
  • 你不需要 any 演员表。 Derived 已经是 IBase 类型,而 Derived* 可以转换为 IBase*(反之则不然)。另外,在修正了很多错别字之后,I cannot reproduce the problem
  • dynamic_cast 在这种情况下不需要。 IBase* basePtr = &amp;d 可以正常工作。从基础到派生时,您需要动态转换。
  • 如果你真的需要将你的对象向上转换为它的基类,你也应该重新考虑你的设计。
  • @Jens 我认为你的意思正好相反。向上转换多态类型很常见;它是硬编码的向下转换,通常表示设计错误。

标签: c++ multiple-inheritance dynamic-cast upcasting


【解决方案1】:
class FileSystem1 : public IFile, public IDirectory

让我们坐下来想一想。这断言FileSystem1 (或更正式地说,在任何可能的情况下,可以用来代替)IFileIDirectory

至少在大多数人使用这些术语时,事情根本不是这样的。正如大多数人使用的术语一样,文件系统包含一些东西,每一个都可以是文件或目录:

class FS_node {
    virtual std::string name() const { return name_; }
    // ...
    virtual ~FS_node = default;
};

class File : public FS_node {
    // ...
};

class Directory : public FS_node { 
    // ...
};

class FileSystem { 
    std::vector<FS_node *> nodes;
public:
    // ...
};

现在,从表面上看,您必须处理两个完全独立的文件系统。有很多方法可以做到这一点。一种可能性是有一个定义文件系统接口的基本 FileSystem 类,然后有两个派生类在操作系统级别根据两个单独的 API 实现该接口。

另一种可能性是实现类似的功能,但不是使用继承,而是在实例化 FileSystem 对象时将 API 接口类指定为模板参数:

template <class Api>
class FileSystem {
    Api api;
public:
    FileSystem(Api const &api) : api(api) {}

    // FS functions for finding files and such go here,
    // each implemented via the `Api` passed as a parameter
};

至于使用模板和继承之间的区别:它与往常几乎相同:模板是静态的,因此如果(例如)您想要可以在编译时指定为 Windows 还是 Linux 编译的代码,模板应该工作得很好。另一方面,如果您正在处理类似文件系统的单个集合,并且该集合可能包含对象的混合,每个对象代表不同的文件系统,并且您希望能够透明地处理所有这些对象在运行时,您可能需要使用继承层次结构。

但至少在我看来,您的 FileSystem 源自 FileDirectory 类的原始设计几乎可以肯定是一个非常严重的错误。我们可能需要更多地了解您正在做什么,才能确定哪种方法真正是最佳的,但可能并非如此。

暂时忽略所有关于设计的内容,看看如何从指针转换为派生到点到基的问题,我们确实只有两种情况非常重要。如果您使用公共继承(如问题所示),则转换根本不需要强制转换:

FileSystem1 foo1;

IFile *fileBase = &foo1;      // No problem.
IDirectory *dirBase = &foo1;  // Likewise

如果您使用私有派生,那么您刚刚发现了一种非常罕见的情况:您实际上需要使用 C 风格的强制转换来正确地进行转换:

class Base1 {};
class Base2 {};

// Note: private derivation: 
class Derived : Base1, Base2 {};

Derived d;
Base1 *b1 = (Base1 *)&d;
Base2 *b2 = (Base2 *)&d;

对于这种特殊情况(将派生类转换为不可访问的基类),“新”C++ 类型转换都无法完成这项工作——您必须使用 C 风格类型转换。

【讨论】:

  • 嗨@jerry-coffin,感谢您提供详细信息。它是一个嵌入式软件。我们有不同类型的物理驱动器,它们具有不同的文件系统(比如 Fat、NTFS)。我们系统的限制是不允许动态内存(我们甚至不能使用 STL)。因此,关系必须是公司;一旦我们创建了文件系统,也必须创建特定的文件系统。所以,第一个建议对我不起作用,我不能在这里使用需要在文件系统之外单独实例化的指针。
  • 对于第二个推荐(模板),我试图用文件路径抽象出所有这些细节。我的想法是如果路径显示物理驱动器 X(NTFS),只需隐式初始化 NTFS,因此用户应用程序在访问文件时不必知道文件系统。所以,我使用一个模板,我需要像下面这样传递低级文件系统实例;我试图避免 FileSystemInstance f("0:/A.txt"); FileSystemInstance f("1:/A.txt");
【解决方案2】:

看来问题出在 Union 数据结构上。 例如,如果我在以下工作时使用联合;

FileSystem fs; 
fs.fs1.Func();

下面的不行

FileSystem fs; 
FileSystem1* fs1 = &fs.fs1; (No casting, the same type)
fs1->Func();

我遇到了第二种情况的异常,它以某种方式找不到虚函数表(vtable 的访问冲突;0xCCCCCCCC)。对我来说没有意义。

如果我将 Union 更改为 Class 类型,则相同的代码可以工作。它必须与联盟本身有关。我需要好好研究它,或者放弃这个想法。谢谢你们。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-30
    • 2016-08-09
    • 1970-01-01
    • 1970-01-01
    • 2013-10-24
    • 1970-01-01
    • 2015-10-26
    • 1970-01-01
    相关资源
    最近更新 更多