Как я могу изменить класс объекта на подкласс его текущего класса в C ++? - PullRequest
3 голосов
/ 28 марта 2010

У меня есть массив указателей на базовый класс, так что я могу заставить эти указатели указывать на (разные) подклассы базового класса, но по-прежнему взаимодействовать с ними. (на самом деле только пару методов, которые я сделал виртуальными и перегруженными) Мне интересно, могу ли я избежать использования указателей, и вместо этого просто сделать массив базового класса, но есть какой-то способ установить класс в подкласс моего Выбор. Я знаю, что должно быть что-то , указывающее класс, так как он должен использовать это для поиска указателя функции для виртуальных методов. Кстати, все подклассы имеют одинаковые ivars и расположение.

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

Спасибо

РЕДАКТИРОВАТЬ: ВСЕ ПОДКЛАССЫ (и, если необходимо), БАЗОВЫЙ КЛАСС ИМЕЕТ ОДИН И ЭТО ОДИН РАЗМЕР / РАЗМЕР

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

Проще говоря, я пытаюсь сделать это

class base {
int ivars[100];
public:
virtual char method() = 0;
}

template <char c>
class subclass : base {
public:
  char method() {
    return c;
  }
}

base things[10];
some_special_cast_thingy(things[2], subclass);
printf("%c", things[2].method());

очевидно, что это намного сложнее, но что касается занятий / чего бы то ни было, это почти все, что я ищу. Это, вероятно, особенность языка, кстати.

Ответы [ 5 ]

2 голосов
/ 28 марта 2010

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

1 голос
/ 28 марта 2010

Проблема, с которой вы столкнулись, связана с распределением памяти. Когда массивы выделены, они должны содержать хранилище для всех своих элементов. Позвольте мне привести (очень упрощенный) пример. Скажем, у вас есть классы, настроенные так:

class Base
{
public:
  int A;
  int B;
}

class ChildOne : Base
{
public:
  int C;
}

class ChildTwo : Base
{
public:
  double C;
}

Когда вы выделяете Base[10], каждому элементу в массиве потребуется (в типичной 32-битной системе *) 8 байт памяти: достаточно для хранения двух 4-байтовых байтов. Однако классу ChildOne требуется 8 байт памяти своего родителя, плюс дополнительные 4 байта для его члена C. Класс ChildTwo нуждается в 8 байтах своего родителя, плюс дополнительные 8 байтов для его double C. Если вы попытаетесь вставить любой из этих двух дочерних классов в массив, выделенный для 8-байтового Base, вы переполните свое хранилище.

Причина работы массивов указателей в том, что они имеют постоянный размер (по 4 байта в 32-битной системе), независимо от того, на что они указывают . Указатель на Base совпадает с указателем на ChildTwo, несмотря на то, что последний класс в два раза больше.

Оператор dynamic_cast позволяет вам выполнять безопасное переключение типов, чтобы изменить Base* на ChildTwo*, поэтому он решит вашу проблему в данном конкретном случае.

Кроме того, вы можете отделить логику обработки от хранилища данных ( Pattern Pattern ), создав макет класса примерно так:

class Data
{
public:
  int A;
  int B;

  Data(HandlerBase* myHandler);
  int DoSomething() { return myHandler->DoSomething(this) }
protected:
  HandlerBase* myHandler;
}

class HandlerBase
{
public:
  virtual int DoSomething(Data* obj) = 0;
}

class ChildHandler : HandlerBase
{
public:
  virtual int DoSomething(Data* obj) { return obj->A; }
}

Этот шаблон будет уместен в тех случаях, когда алгоритмическая логика DoSomething может потребовать значительной настройки или инициализации, которая является общей для большого количества объектов (и может обрабатываться в конструкции ChildHandler), но не универсальной ( и, следовательно, не подходит для статического члена). Затем объекты данных сохраняют согласованное хранилище и указывают на процесс-обработчик, который будет использоваться для выполнения их операций, передавая себя в качестве параметра, когда им нужно что-то вызвать. Data объекты этого вида имеют согласованный, предсказуемый размер и могут быть сгруппированы в массивы для сохранения ссылочной локализации, но при этом обладают всей гибкостью обычного механизма наследования.

Обратите внимание, что вы все еще строите то, что равносильно массиву указателей, - они просто укрыты другим слоем глубоко под фактической структурой массива.

* Для придирки: да, я понимаю, что числа, которые я дал для распределения памяти, игнорируют заголовки классов, информацию vtable, отступы и большое количество других потенциальных соображений компилятора. Это не должно быть исчерпывающим.

Редактировать часть II: Все следующие материалы неверны. Я разместил его на макушке, не проверяя его, и перепутал способность переосмысливать два не связанных указателя с возможностью создавать два не связанных класса. Mea culpa , и спасибо Чарльзу Бейли за то, что он указал на мою оплошность.

Общий эффект все еще возможен - вы можете принудительно извлечь объект из массива и использовать его как другой класс - но это требует взятия адреса объекта и принудительного наведения указателя на новый тип объекта, который побеждает теоретическую цель избежать разыменования указателя. В любом случае, моя первоначальная точка зрения - что это ужасная «оптимизация», которую я пытаюсь сделать в первую очередь - все еще верна.

Редактировать: Хорошо, я думаю, что с вашими последними правками я понял, что вы пытаетесь сделать. Я собираюсь дать вам решение здесь, но, пожалуйста, ради любви ко всему святому, поклянись, что ты никогда не будешь использовать это в рабочем коде . Это инженерное любопытство, не хорошая практика.

Похоже, вы пытаетесь избежать разыменования указателя (возможно, в качестве микрооптимизации производительности?), Но все же хотите гибко вызывать субметоды для объектов. Если вы точно знаете, что ваши базовые и производные классы имеют одинаковые размеры - и only способ, которым вы собираетесь это знать, - это изучить макет физического класса, сгенерированный компилятором, поскольку он может вносить всевозможные корректировки по своему усмотрению, а спецификация не дает никаких гарантий - тогда вы можете использовать reinterpret_cast для принудительного обращения к родителю как к дочернему элементу в массиве.

class Base
{
public:
  int A;
  int B;

  void DoSomething();
}

class Derived : Base
{
  void DoSomething();
}

void DangerousGames()
{
  // create an array of ten default-constructed Base on the stack
  Base items[10];
  // force the compiler to treat the bits of items[5] as a Derived,
  // and make a ref
  Derived& childItem = reinterpret_cast<Derived>(items[5]);
  // invoke Derived::DoSomething() using the data bits of items[5], 
  // since it has an identical layout
  childItem.DoSomething();
}

Это избавит вас от разыменования указателя и не приведет к снижению производительности, поскольку reinterpret_cast не является приведением во время выполнения, это по сути переопределение компилятора, которое говорит: «Независимо от того, что вы думаете, вы знаете, я знаю, что делаю, заткнись и сделай это. «Небольшой недостаток» заключается в том, что он делает ваш код очень хрупким, потому что любое изменение макета Base или Derived, инициированное вами или компилятором, приведет к краху всего этого, с тем, что, вероятно, будет чрезвычайно тонким и почти невозможным для отладки неопределенного поведения. Опять же, никогда не использует это в рабочем коде . Даже в наиболее критичных для производительности системах реального времени стоимость разыменования указателя стоит всегда , по сравнению со сборкой того, что составляет ядерную бомбу с пусковым механизмом в середине вашей кодовой базы.

1 голос
/ 28 марта 2010

Звучит как шаблон состояния или шаблон стратегии.

Отделите данные от класса и получите массив, содержащий данные и указатель на базовый класс. Установите указатель так, чтобы он указывал на другой подкласс по мере необходимости, но методы базового класса принимают указатель или ссылку на элемент массива для доступа к данным. Подклассы реализуют методы базового класса, но в остальном не имеют данных, кроме того, что передается в методы.

class Base;

class Data{
  string name;
  int age;
  Base* base;
};

class Base{
  virtual void method1(Data&)=0;
  // other methods
};

class Subclass1: public Base{
  void method1(Data& data){ data.age=0; }
};

vector<Data> dataVector;
Subclass1 subclass1;
Data* someData = ...
someData->base=&subclass1;

someData->base->method1(*someData);
0 голосов
/ 28 марта 2010

Массив должен быть точного типа, потому что компилятор должен иметь возможность вычислять точную позицию каждого элемента. Таким образом, вы не можете иметь смесь подклассов или даже рассматривать массив подкласса как массив базового класса.

0 голосов
/ 28 марта 2010

вы можете использовать массив указателей. Если вам нужен доступ к частям интерфейса производных классов, вы можете использовать dynamic_cast. http://en.wikipedia.org/wiki/Dynamic_cast

...