Система преобразования вариантов

Я написал вариантный класс, который будет использоваться в качестве основного типа в динамическом языке, что в конечном итоге позволит использовать 256 различных типов значений (заголовок — это беззнаковый байт, фактически используются только 20). Теперь я хочу реализовать приведение/преобразование между типами.

Моя первоначальная мысль была о таблице поиска, но требуемый объем памяти делает ее непрактичной.

Какие есть альтернативы? Прямо сейчас я рассматриваю еще три метода, основанные на исследованиях и предложениях других людей:

  1. Сгруппируйте типы в более крупные подмножества, такие как числовые, коллекционные или другие.
  2. Создайте интерфейс преобразования с методами CanCast(from, to) и Cast(Variant) и разрешите добавление классов, реализующих этот интерфейс, в список, который затем можно будет проверить, чтобы узнать, может ли какой-либо из классов преобразования выполнить приведение.
  3. Аналогично (1), но создайте несколько мастер-типов, а преобразование представляет собой двухэтапный процесс от исходного типа к мастер-типу, а затем снова к окончательному типу.

Какая система будет лучшей?

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


person Matt    schedule 05.05.2011    source источник
comment
Еще одна возможность, о которой я не упомянул, - это использование сторонних реализаций. Я сделал некоторое фоновое чтение в COM VARIANTs, но я действительно не хочу этого делать по ряду причин (в основном из-за того, что я хочу это сделать, и я не уверен, что объединение всех возможных типов является лучшим способ сделать это).   -  person Matt    schedule 12.05.2011


Ответы (3)


Моя система очень «тяжелая» (много кода), но очень быстрая и многофункциональная (кроссплатформенный C++). Я не уверен, как далеко вы хотели бы зайти со своим дизайном, но вот основные части того, что я сделал:

DatumState — класс, содержащий "перечисление" для "типа" и собственное значение, которое представляет собой "объединение" всех типов-примитивов, включая void*. Этот класс не связан со всеми типами и может использоваться для любых нативных/примитивных типов и типа "ссылка на" void*. Поскольку "enum" также имеет контекст "VALUE_OF" и "REF_TO", этот класс может быть представлен как "полностью содержащий" float (или какой-либо примитивный тип) или "ссылающийся, но не владеющий" float (или какой-либо примитивный тип). тип). (На самом деле у меня есть контексты "VALUE_OF", "REF_TO" и "PTR_TO", поэтому я могу логически хранить значение, ссылку, которая не может быть нулевой, или указатель, который может быть нулевым или нет и который, как я знаю, мне нужно удалить или нет.)

Datum — класс, полностью содержащий DatumState, но расширяющий свой интерфейс для включения различных "хорошо известных" типов (таких как MyDate, MyColor, MyFileName и т. д.). известные типы фактически хранятся в void* внутри члена DatumState. Однако, поскольку часть "enum" DatumState имеет контекст "VALUE_OF" и "REF_TO", она может представлять "pointer-to-MyDate" или "value-of-MyDate".

DatumStateHandle – вспомогательный класс шаблона, параметризованный с помощью (общеизвестного) типа (например, MyDate, MyColor, MyFileName и т. д.). Это метод доступа, используемый Datum для извлечения состояния из общеизвестного типа. Реализация по умолчанию работает для большинства классов, но любой класс с определенной семантикой для доступа просто переопределяет конкретную параметризацию/реализацию шаблона для одной или нескольких функций-членов в этом классе шаблона.

Macros, helper functions, and some other supporting stuff. Чтобы упростить «добавление» известных типов в мои Datum/Variant, я счел удобным централизовать логику в нескольких макросах, предоставить некоторые вспомогательные функции, такие как перегрузка операторов, и установить некоторые другие соглашения. в моем коде.

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

Например, вы можете создать набор целых чисел и проиндексировать их:

int my_ints[10];
Datum d(my_ints, 10/*count*/);
for(long i = 0; i < d.count(); ++i)
{
  d[i] = i;
}

Точно так же некоторые типы данных индексируются строками или перечислениями:

MyDate my_date = MyDate::GetDateToday();
Datum d(my_date);
cout << d["DAY_OF_WEEK"] << endl;
cout << d[MyDate::DAY_OF_WEEK] << endl; // alternative

Я могу хранить наборы элементов (изначально) или наборы Datums (оборачивая каждый элемент). В любом случае я могу рекурсивно «развернуть»:

MyDate my_dates[10];
Datum d(my_dates, 10/*count*/);
for(long i = 0; i < d.count(); ++i)
{
  cout << d[i][MyDate::DAY_OF_WEEK] << endl;
}

Кто-то может возразить, что моя семантика "REF_TO" и "VALUE_OF" избыточна, но они были необходимы для "установки-развертки".

Я сделал это "Variant" с девятью различными проектами, и мой текущий - "самый тяжелый" (больше всего кода), но тот, который мне нравится больше всего (почти самый быстрый с довольно небольшим размером объекта), и я устарели другие восемь дизайнов для моего использования.

«Минусы» моего дизайна:

  1. Доступ к объектам осуществляется через static_cast<>() из void* (типобезопасный и довольно быстрый, но требуется косвенное обращение; но побочным эффектом является то, что дизайн поддерживает хранение «null».)
  2. Компиляции длиннее из-за общеизвестных типов, которые доступны через интерфейс Datum (но вы можете использовать DatumState, если вам не нужны API общеизвестных типов).

Независимо от вашего дизайна, я бы рекомендовал следующее:

  1. Используйте "enum" или что-то подобное, чтобы указать "тип" отдельно от "значения". (Я знаю, что вы можете сжать их в один «int» или что-то еще с битовой упаковкой, но это медленно для доступа и очень сложно поддерживать по мере появления новых типов.)

  2. Опирайтесь на шаблоны или что-то еще для централизации операций с механизмом обработки для конкретного типа (переопределения) (при условии, что вы хотите обрабатывать нетривиальные типы).

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

Удачи!

person charley    schedule 13.05.2011
comment
Интересно, что вы реализовали свой вариантный класс очень похоже на то, как это сделал я. Мне нужно провести дополнительные исследования и, возможно, реализовать это :) Большое спасибо @charley - person Matt; 13.05.2011
comment
Меня больше всего интересовала реальная система кастинга, поэтому наиболее важной частью вашего поста была static_cast<>(), поскольку она привела меня к эта страница на msdn, которая описывает приведение типов во время выполнения в C++. Я знаю, что никогда не помечал свой вопрос языком, так как думал, что это будет неуместно, но это идеально подходит для того, что я хочу. При этом я охватываю все, кроме нескольких преобразований, которые легко проверить и сделать самому. - person Matt; 13.05.2011

Сделал нечто подобное.

Вы можете добавить еще один байт в «заголовок», указав тип, который он действительно хранит.

Пример на языке программирования в стиле C:

typedef
enum VariantInternalType {
  vtUnassigned = 0;
  vtByte = 1;
  vtCharPtr = 2; // <-- "plain c" string
  vtBool = 3;
  // other supported data types
}

// --> real data
typedef
struct VariantHeader {
  void* Reserved; // <-- your data (byte or void*)
  VariantInternalType VariantInternalType;  
}

// --> hides real data
typedef
  byte[sizeof(VariantHeader)] Variant;

// allocates & assign a byte data type to a variant
Variant ByteToVar(byte value)
{
  VariantHeader MyVariantHeader;
  Variant MyVariant;

  MyVariantHeader.VariantInternalType = VariantInternalType.vtByte;
  MyVariantHeader.Reserved = value;  

  memcpy (&MyVariant, &MyVariantHeader, sizeof(Variant));

  return myVariant;
}

// allocates & assign a char array data type to a variant
Variant CharPtrToVar(char* value)
{
  VariantHeader MyVariantHeader;
  Variant MyVariant;

  MyVariantHeader.VariantInternalType = VariantInternalType.vtByte;
  MyVariantHeader.Reserved = strcpy(value);  

  // copy exposed struct type data to hidden array data
  memcpy(&MyVariant, &MyVariantHeader, sizeof(Variant));

  return myVariant;
}

// deallocs memory for any internal data type
void freeVar(Variant &myVariant)
{
  VariantHeader MyVariantHeader;

  // copy exposed struct type data to hidden array data
  memcpy(&MyVariantHeader, &MyVariant, sizeof(VariantHeader));

  switch (MyVariantHeader.VariantInternalType) {
    case vtCharPtr:
      strfree(MyVariantHeader.reserved);
    break;

    // other types

    default:
    break;
  }

  // copy exposed struct type data to hidden array data
  memcpy(&MyVariant, &MyVariantHeader, sizeof(Variant));
}

bool isVariantType(Variant &thisVariant, VariantInternalType thisType)
{
  VariantHeader MyVariantHeader;

  // copy exposed struct type data to hidden array data
  memcpy(&MyVariantHeader, &MyVariant, sizeof(VariantHeader));

  return (MyVariant.VariantInternalType == thisType);
}

// -------

void main()
{
  Variant myVariantStr = CharPtrToVar("Hello World");
  Variant myVariantByte = ByteToVar(42);

  char* myString = null;
  byte  myByte = 0;

  if isVariantType(myVariantStr, vtCharPtr) {
    myString = VarToCharPtr(myVariantStr);
    // print variant string into screen
  }

  // ...    
}

Это всего лишь предложение, и оно не проверено.

person umlcat    schedule 06.05.2011
comment
Это неплохая идея, но я думаю, что поработаю над ней, чтобы увидеть, возможно ли ее реализовать. Я пока не собираюсь принимать это как ответ: P - person Matt; 07.05.2011
comment
@Mat Нет проблем, удачи ;-) - person umlcat; 07.05.2011

Возможно, вы уже выполнили вычисления, но объем памяти, необходимый для таблицы поиска, не такой уж большой.

Если вам просто нужно проверить, совместимы ли типы, вам нужно (256 * 256)/2 бита. Для этого требуется 4k памяти.

Если вам также нужен указатель на функцию преобразования, вам нужно (256 * 256)/2 указателя. Для этого требуется 128 КБ памяти на 32-разрядной машине и 256 КБ на 64-разрядной машине. Если вы хотите сделать какой-то низкоуровневый макет адресов, вы, вероятно, можете уменьшить его до 64 КБ как на 32-разрядных, так и на 64-разрядных машинах.

person Kannan Goundan    schedule 26.05.2011