Приветствую всех. Заранее прошу прощения за большой объем текста — я не нашел спойлеров, что бы код закатать под них.
Суть проблемы такова — у меня опять стойкое ощущение, что я изобретаю велосипед, но тем не менее ничего удовлетворяющего потребностям я не нашел (вполне вероятно, плохо искал). Значит к делу:
Задача такова — модуль D (некий демон) принимает от клиента xml файл c 1..n аргументов. Так же в этом файле указан модуль X и функция этого модуля , которая должна обработать запрос. Модуль D обрабатывает xml и обертывает аргументы в некий контейнер, который передает в вызываемую функцию.
Ремарка_1: модуль D знать не знает до поступления запроса ни о модуле X, ни о принимаемых/возращаемых аргументах функции.
Далее, из функции (dlopen,dlsym) модуль D получает аналогичный контейнер, с 1..n значениями, причем в контейнер может быть вложен аналогичный контейнер n раз (например, возращается таблица, набор данных или и таблица и набор данных, всякое бывает).
Ремарка_2: Так как возращаемая структура создается динамически, то логично использовать массив ссылок на собственно значения, а так как создается структура в функции, то по выходе из нее и разворачивании стека без динамического выделения памяти по указателям останеться пфу, в смысле ничего.
В принципе искомый контейнер по своему поведению напоминает ассоциативный массив в PHP. Ах, да, чуть не забыл. Аргументы естественно не типизированы, в смысле предполагается как набор интегральных значений разного типа, так и строки.
Собственно вопрос 1 — если я изобретаю велосипед — то подскажите мне варианты таких динамических рекурсивных нетипизированых контейнеров.
Вопрос 2 — в листинге ниже (жалко нет спойлеров) приведены класса, в которых я попытался реализовать данный контейнер. Но там есть две ошибки:
Так как вызов функции происходит в потоке, то то ли у меня где-то утечка памяти, то ли я просто исчерпываю стек, но если я сколько-нибудь большой объем данных возвращаю — получаю сигнал SIGSEGV от других библиотек, используемых в этом же потоке (libxml2 и даже вызов dlclose).
Вторая проблемка — в механизме виртуальных функций. Так как в операторе [] я выделяю память под Variant, так как еще не знаю, что будет справа при копирующем присваивании в инструкции, например, arr['abc']=val; , то если по адресу располагается вложенный массив, то и освобождение для него возывается из род. класса Variant, а не из класса Array, как хотелось бы. Конечно есть вариант отказаться от виртуальной функции и написать FreeArray, но как-то это некрасиво...
Собственно код:
MiracleBox.h
typedef enum {eNone=0, eInt, eString, eBool, eDouble, eDate, eArray} VariableTypes;
class Variant {
protected:
//Для скалярных величин - указатель на хранящееся в Value значение
//Для массивов - указатель на массив
void* pVal;
//Тип значения
VariableTypes Type;
//Для скалярных величин - размер значения
//Для массивов - кол-во элементов
uint Size;
//Имя переменной/ключ ассоциативного массива
char* Name;
public:
Variant(): pVal(0),Type(eNone),Size(0),Name(0){};//конструктор по умолчанию, создающий ссылку на пустое значение
Variant(int val);
Variant(const char* val);
Variant(string val);
Variant(bool val);
Variant(double val);
Variant(time_t val);
Variant(const Variant& val);
virtual ~Variant(){};
Variant& operator=(const Variant& val);
virtual void Free();
//Обнуление переменной без очищения памяти
virtual void SetNull(){pVal=0;Size=0;Type=eNone;}
void SetName(const char* val);
char* GetName() const {return Name;}
VariableTypes GetType() const {return Type;}
//void* GetValuePtr() const {return pVal;}
string ToString() const {return ToCString();}
char* ToCString() const;
int ToInt() const {return ToDouble();}
double ToDouble() const;
bool ToBool() const {return ToDouble();}
time_t ToTimestamp() const {return ToDouble();}
};
///////////////////////////////////////////////
//класс Array
class Array: public Variant {
public:
Array();
//не реализован, так как va-макросы не работают с non-POD типами, n объектов передать нельзя.
//Array(Variant* ...);
//
//explicit Array(uint count);
Array(const Array& val);
virtual ~Array(){};
Variant& operator[](uint index);
Variant& operator[](const char* key);
Array& operator=(const Array& val);
void Push (Variant& val,const char* key=0);
virtual void Free();
bool IsEmpty(){ return (Size==0);}
int GetArraySize() const { return Size;}
};
MiracleBox.cpp
/////////////////////////////////////////////////////////////////////
//класс Variant
//конструкторы
Variant::Variant(int val){//конструктор для целых чисел
pVal = new int(val);
Type = eInt;
Size = sizeof(int);
}
Variant::Variant(const char* val){
int len = strlen(val)+1;
char* buf = new char[len];
strcpy(buf,val);
pVal = buf;
Type = eString;
Size = len-1;
}
Variant::Variant(string val){
int len = val.size()+1;
char* buf = new char[len];
strcpy(buf,val.c_str());
pVal = buf;
Type = eString;
Size = len-1;
}
Variant::Variant(double val){
pVal = new double(val);
Type = eDouble;
Size = sizeof(double);
}
Variant::Variant(bool val){
pVal = new bool(val);
Type = eBool;
Size = sizeof(bool);
}
Variant::Variant(time_t val){
pVal = new time_t(val);
Type = eDate;
Size = sizeof(time_t);
}
Variant::Variant(const Variant& val){
pVal = val.pVal;
Type = val.Type;
Size = val.Size;
}
Variant& Variant::operator=(const Variant& val){
if(this!=&val){
this->Free();
pVal = val.pVal;
Type = val.Type;
Size = val.Size;
}
return *this;
}
void Variant::SetName(const char* val){
if(val && val[0]){//если строка существует и не равна пустой строке
delete Name;
int len = strlen(val)+1;
Name = new char[len];
strcpy(Name,val);
}
}
void Variant::Free(){
//Так как метод вызывается из копирующего присваивания, а значит и при присваивании
//индексированному элементу массива(вновь созданный элемент массива до присваивания имеет тип
//eNone), то для Variant == NULL очистка не происходит.
if(Type){
switch(Type){
case (eInt):
delete (int*)pVal;
break;
case (eBool):
delete (bool*)pVal;
break;
case (eString):
delete (char*)pVal;
break;
case (eDate):
delete (time_t*)pVal;
break;
case (eDouble):
delete (double*)pVal;
break;
case (eArray)://так как функция фиртуальная - сюда никогда не должны попадать, но попадаем, так как в operator[] выделяем память под Variant
((Array*)pVal)->Free();
delete (Variant*)pVal;
default:
throw new MiracleBoxException("Ошибка освобождения памяти в Variant");
}
}
}
double Variant::ToDouble() const{
switch(Type){
case(eBool):
case (eInt):
case (eDate):
case (eDouble):
return *(double*)pVal;
case (eString):
case (eArray):
return Size;
case eNone:
default:
return 0;
}
}
char* Variant::ToCString() const{
char* s;
switch(Type){
case(eBool):
case (eInt):
case (eDate):
sprintf(s,"%d",*(int*)pVal);
return s;
case (eDouble):
sprintf(s,"%f",*(double*)pVal);
return s;
case (eString):
return (char*)pVal;
case (eArray):
return s;
case eNone:
default:
return s;
}
}
/////////////////////////////////////////////////////
//класс Array
Array::Array(){
pVal = 0;//new Variant*[0];
Size = 0;
Type = eArray;
}
Array::Array(const Array& val){
pVal = val.pVal;
Type = val.Type;
Size = val.Size;
}
Array& Array::operator =(const Array& val){/////////
if(this!=&val){
this->Free();
pVal = val.pVal;
Type = val.Type;
Size = val.Size;
}
return *this;
}
Variant& Array::operator[](uint index){
Variant** p = (Variant**)pVal;
if(index < Size && index >= 0)
return *p[index];
else
throw MiracleBoxException("Range exception");
}
Variant& Array::operator[](const char* key){
Variant** p = (Variant**)pVal;
for(uint i = 0; i < Size; ++i){
if(p[i]->GetName()){
if(!strcmp(key,p[i]->GetName())){
return *p[i];
}
}
}
//ключ не найден, создаем новый элемент
Variant v;
Push(v,key);
return *((Variant**)pVal)[Size-1];
}
void Array::Push(Variant& val, const char* key){
Variant* pNew = new Variant();
*pNew = val;
Variant** p = (Variant**)pVal;
uint newArrSize=Size+1;
Variant** temp = new Variant*[newArrSize];
for(uint i = 0; i < Size; ++i){
temp[i] = p[i];
}
delete[] p;
temp[Size] = pNew;
temp[Size]->SetName(key);
pVal = temp;
Size = newArrSize;
}
void Array::Free(){
Variant** p = (Variant**)pVal;
for(uint i = 0;i < Size; i++){
try{
delete p[i]->GetName();
if(p[i]->GetType()==eArray){
Array* a = static_cast<Array*>(p[i]);
a->Free();
}else{
p[i]->Free();
}
delete p[i];
}
catch(...){
//int i = errno;
//char* p = strerror(errno);
}
}
delete[] p;
}
Пример функции, в которой принимаются значения от модуля D и формируется возвращаемый объект
#define ARGS (*args)
#define RETVAL (*retval)
extern "C" int RegisterUser(Array* args, Array* retval){
Array test;
test["age"]=35;
test["mark"]=4.73;
Array row1,row2;
row1["id"] = 1;
row1["name"] = "Vasya";
row1["date"] = time(NULL);
row2["id"] = 2;
row2["name"] = "Petya";
row2["date"] = time(NULL);
Array table;
table.Push(row1);
table.Push(row2);
int result=0;
string sessionID;
UserModule um;
result = um.RegisterUser(ARGS["login"].ToString(), ARGS["password"].ToString() ,sessionID);
RETVAL["sessionID"] = sessionID;
RETVAL["success"] = (result==0);
RETVAL["test_array"] = test;
RETVAL["test_table"] = table;
return 0;
}
Не судите строго и не кидайтесь тапками, после многих лет работы с виндоус и нескольких лет работы с .NET пришлось перейти к linux и вернуться к забытым с/с++.