C ++ Upcasting из множественного наследования;источник не полиморфный - PullRequest
0 голосов
/ 15 октября 2019

Я новичок в 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 дляupcasting из множественного наследования, верно?

Когда я это делаю, я получаю сообщение об ошибке "тип источника не полиморфный"

Мои базовые классы являются чисто абстрактными классами, поэтому у них есть по крайней мере одинвиртуальный метид, так что все должно быть в порядке, правда, но почему я получаю эту ошибку? Речь идет о множественном наследовании?

Редактировать: Здесь есть еще один слой. Мой производный класс должен содержать два разных типа экземпляров, но мои экземпляры действительно огромные переменные, так что, как разработчик 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);

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

Спасибо.

Ответы [ 2 ]

2 голосов
/ 15 октября 2019
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.

0 голосов
/ 15 октября 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, тот же код работает. Это должно быть о самом Союзе. Мне нужно хорошо изучить это или оставить идею. Спасибо вам всем.

...