Всем привет, разрабатываю язык программирования на c++ (надо для учёбы), но в плюсах я пока новичок, и отсюда возникают различные вопросы из раздела "а как лучше?"
Собственно, в чём проблема: нужен менеджер динамической памяти (грубо говоря, это штука, которая какому-то имени присваивает какие-то данные). Тип данных может меняться (это может быть, например, массив из чаров или инстанс какого-то класса — не важно). К текущему моменту я использовал объединения (union), в которых я просто перечислял возможные варианты типов.
union JMemoryDataPointer {
int* intData;
float* floatData;
char* stringData;
bool* boolData;
} data;
Но недавно подумал, что можно сделать какой-то класс, например, Data и потом наплодить ему детей DataInteger, DataThing и т.д.
Ну, и собственно, вопрос: какой вариант лучше и почему? (хотя бы по вашему мнению)
У вашего метода есть несколько серьёзных изъянов:
Необходимость нисходящего приведения от базового класса к дочернему (в случае @KoVadim-а) или небезопасного обращения к полям объединения (в вашем случае). При этом можно легко опечататься и случайно преобразовать не к тому типу с плачевным результатом.
Отсутствие контроля обработки всех возможных типов в перечислении. Особо остро данная проблема встанет при добавлении поддержки нового элемента перечисления в изрядно разросшуюся программу.
Отсутствие возможности хранения чего-либо, отличного от примитивных типов данных. Пока это несущественно, но в будущем может возникнуть необходимость инкапсуляции некой логики в тип о типе данных вашего языка программирования. Примером такой логики может служить проверка значений параметров при их изменении. Из-за этого поля придётся делать закрытыми, выставляя вместо них методы. Следом за методами появится и конструктор, который нельзя будет вызвать из union
-а.
Для решения первой проблемы был придуман шаблон проектирования «Посетитель» («Visitor»). Его суть заключается в том, что информация о текущем типе хранимого значения известна только контейнеру (union
) и наружу не передаётся. Как же тогда работать с хранимой переменной, спросите вы. Очень просто. При необходимости доступа к переменной мы передаём контейнеру совокупность обработчиков, каждый из которых предназначен для работы со своим определённым типом. Контейнер же сам выбирает из них нужный и вызывает его с преобразованным в соответствующий тип значением.
Удобной реализацией этого шаблона проектирования является класс boost::variant
. Для обработки хранимого типа у него имеется функция boost::apply_visitor()
, принимающая экземпляр некоего класса с перегрузками метода T_out operator()(T_in)
. Да-да, обработчики могут ещё и возвращать значения по результатам работы с переменной. В придачу этот метод может быть ещё и шаблонным, покрывая сразу несколько (или все) типов за раз.
Наконец, этот класс решает две оставшиеся проблемы ещё на этапе компиляции.
Рассмотрим его использование на примере:
#include <boost/variant/variant.hpp>
// Вспомогательный класс владеющего указателя (урезанный аналог std::unique_ptr из C++11)
template<class T> class MyUniqueArrayPtr
{
T* _ptr;
public:
explicit MyUniqueArrayPtr(size_t count) : _ptr(new[] T(count)) {}
~MyUniqueArrayPtr() {delete[] _ptr;}
T* operator*() const {return _ptr};
T* operator->() const {return _ptr};
};
// Контейнер информации о переменных
typedef variable_t boost::variant<
MyUniqueArrayPtr<int>,
MyUniqueArrayPtr<float>,
MyUniqueArrayPtr<char>,
MyUniqueArrayPtr<bool>
>;
// Класс для получения строкового представления значения переменной (для редактора кода
// или отладчика)
class StringifyingVisitor : public boost::static_visitor<std::string>
{
public:
// Шаблонный метод, преобразующий в строку всё, поддерживаемое std::to_string()
template<class T>
std::string operator()(T val) const
{
// Доступна, начиная с C++11; приведена просто для примера
return std::to_string(*val);
}
};
// Класс для компиляции машинного кода
class CompilationVisitor : public boost::static_visitor<>
{
public:
// Пусть в конструкторе будет передаваться общий тип действия для переменной,
// чтобы компилятор смог наставить машинной команде нужных типозависимых флагов.
explicit CompilationVisitor(Command command)
{
// ...
}
// С boost::variant можно принимать и константные ссылки. А можно и неконстантные.
void operator()(const MyUniqueArrayPtr<int>& val)
{
// ...
}
void operator()(const MyUniqueArrayPtr<float>& val)
{
// ...
}
// ...
};
// Тестовая функция, принимающая откуда-то информацию о переменной
void foo(const variable_t& variable)
{
// Методы первого посетителя шаблонные, поэтому его можно создать прямо по месту
// вызова
std::cout << boost::apply_visitor(StringifyingVisitor(), variable) << std::endl;
// Второй посетитель
CompilationVisitor compiler(someCommand);
boost::apply_visitor(compiler, variable);
}
Когда-то мне что-то подобное нужно было, а использовать шаблоны было нельзя в итоге сделал такую вот штуковину:
github
Смотрите файлы datatilepropertys.h
и datatilepropertys.cpp
там есть методы as_int()
, as_string()
и т.д.
Оборудование для ресторана: новинки профессиональной кухонной техники
Частный дом престарелых в Киеве: комфорт, забота и профессиональный уход
Привет всем! Как посчитать количество вхождений паттерна regex в сроке? Язык С++
На вход в json передаю vector<string>vecs, но выдает ошибку(коммит в конце), помогите решить
Почему вектор инициализируется рандомным значением? Константа ещё не успевает стать -1? Как тогда сделать правильно?