Преобразование 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 для меньшего использования памяти. Объединение имеет только экземпляры двух классов, которые являются производными от чистых абстрактных классов. Итак, я предполагал, что адрес экземпляра объединения также должен указывать смещения экземпляров моего класса, но С++, вероятно, не может знать адрес методов записи членов.

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);

Здесь я не хочу интересоваться, какой экземпляр инициализируется. Я хочу работать только с интерфейсом базового класса. Мне интересно, это как-то возможно с Союзом?

Спасибо.


person muratcakmak    schedule 14.10.2019    source источник
comment
Исправьте код так, чтобы он показывал проблему, а не кучу опечаток.   -  person Pete Becker    schedule 15.10.2019
comment
Вам не нужно никакое приведение. Derived уже относится к типу IBase, а Derived* преобразуется в IBase* (обратное неверно). Кроме того, после исправления множества опечаток я не могу воспроизвести проблему   -  person Yksisarvinen    schedule 15.10.2019
comment
dynamic_cast в данном случае не нужен. IBase* basePtr = &d будет работать нормально. Вам нужен динамический бросок при переходе от базового к производному.   -  person cplusplusrat    schedule 15.10.2019
comment
Опубликуйте минимально воспроизводимый пример. Также уже было указано, что в опубликованном вами коде много опечаток, но не ошибка, о которой вы заявляете.   -  person 463035818_is_not_a_number    schedule 15.10.2019
comment
Если вам действительно нужно преобразовать объект в его базовый класс, вам также следует переосмыслить свой дизайн.   -  person Jens    schedule 15.10.2019
comment
@Jens Я думаю, ты имеешь в виду обратное. Преобразование полиморфного типа очень распространено; это жестко запрограммированное понижение частоты, которое обычно указывает на ошибку дизайна.   -  person cdhowie    schedule 15.10.2019
comment
Мне нужно преобразовать указатель производного класса в один из базовых абстрактных классов. Вам никогда не нужно делать то, что вы описываете, потому что экземпляр производного класса по определению также является базовым классом. Итак, вы просто используете «d», как если бы это была «IBase». Например, если в IBase есть функция foo, вы просто вызываете d.foo(). НЕ ТРЕБУЕТСЯ БРОСОК. Просто попробуйте!   -  person 2785528    schedule 15.10.2019
comment
Прошу прощения у всех, я дал недостающую информацию. У моей проблемы есть еще один слой, я отредактировал свое описание, которое может объяснить, почему я получаю сообщение об ошибке. Это может быть плохо.   -  person muratcakmak    schedule 15.10.2019
comment
Файловая система — это не файл и не каталог. Но в любом случае вы могли бы просто написать IFile* file = &fs.fs1   -  person rustyx    schedule 15.10.2019
comment
Если вы новичок, то вам, вероятно, следует избегать союза. Они редко используются даже экспертами и обычно только для базовых типов, таких как int или char, и для вещей низкого уровня.   -  person Phil1970    schedule 15.10.2019
comment
Хороший дизайн обычно не требует много литья. Часто явное приведение — это работа над плохим дизайном или системными ограничениями.   -  person Phil1970    schedule 15.10.2019


Ответы (2)


class FileSystem1 : public IFile, public IDirectory

Давайте сядем и подумаем об этом на мгновение. Это утверждает, что FileSystem1 является (или, более формально, при любых возможных обстоятельствах может использоваться вместо) либо IFile, либо IDirectory.

По крайней мере, поскольку большинство людей используют эти термины, все обстоит совсем не так. Как большинство людей используют термины, файловая система содержит некоторые элементы, каждый из которых может быть либо файлом, либо каталогом:

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 на уровне ОС.

Другой возможностью было бы реализовать аналогичную функциональность, но вместо использования наследования вы бы указали класс интерфейса API в качестве параметра шаблона при создании экземпляра объекта FileSystem:

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, производным от классов File и Directory, почти наверняка является довольно серьезной ошибкой. Нам, вероятно, нужно знать немного больше о том, что вы делаете, чтобы быть уверенными в том, какой подход действительно оптимален, но это, вероятно, не так.

Если на мгновение отвлечься от всего, что связано с дизайном, и рассмотреть вопрос о том, как преобразовать указатель в производный, чтобы указать в базовый, у нас действительно есть только два случая, которые имеют большое значение. Если вы использовали публичное наследование (как показано в вопросе), преобразование вообще не требует приведения:

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.

person Jerry Coffin    schedule 15.10.2019
comment
Привет @jerry-coffin, спасибо за подробную информацию. Это встроенное программное обеспечение. У нас есть разные типы физических дисков с разными файловыми системами (скажем, Fat, NTFS). Ограничение в нашей системе - динамическая память не разрешена (мы не можем использовать даже STL). Таким образом, отношения должны быть компанизационными; как только мы создадим файловую систему, также должна быть создана определенная файловая система. Итак, первая рекомендация для меня не работает, я не могу работать с указателями здесь, которые требуют отдельного создания вне файловой системы. - person muratcakmak; 15.10.2019
comment
Для второй рекомендации (шаблон) я пытался абстрагироваться от всех этих деталей с помощью пути к файлу. Моя идея заключалась в том, что если путь показывает физический диск X (NTFS), просто неявно инициализируйте NTFS, поэтому пользовательскому приложению не нужно будет знать файловую систему при доступе к файлу. Итак, я использую шаблон, мне нужно будет передать экземпляр файловой системы низкого уровня, как показано ниже; которого я пытался избежать FileSystemInstance‹NTFS› f(0:/A.txt); FileSystemInstance‹FAT› f(1:/A.txt); - person muratcakmak; 15.10.2019

Кажется, проблема в структуре данных Union. Например, если я использую union, пока работает следующее:

FileSystem fs; 
fs.fs1.Func();

ниже не работает

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

У меня есть исключение для второго случая, он почему-то не может найти таблицу виртуальных функций (Нарушение прав доступа для vtable; 0xCCCCCCCC). Не имеет смысла для меня.

Если я изменю тип Union на Class, будет работать тот же код. Это должно быть о самом Союзе. Мне нужно хорошо его изучить, или оставить идею. Спасибо вам всем.

person muratcakmak    schedule 15.10.2019