петък, 23 май 2008 г.

<T>emplate magic

Просто не мога да се сдържа да не се похваля какво извращение успях да подкарам на C++ наскоро. Става дума за тип, в който може да се слага всеки друг тип (по един във всеки момент) и да се взима текущия тип (като се каже какъв е). Т.е исках да върви подобен код:


generic x;
x.set (5);
x.get <int> ();//--> 5
x.set (std::string ("alabala"))
x.get <std::string> ();//--> alabala

//type checking
x.is <int> ();//false


Та значи горното нещо в началото ми се струваше сън, докато не осъзнах, че е напълно реално - нужни са само 2 помощни класа (т.е общо 3) и горният код РАБОТИ.

Накратко:
1. Абстрактен базов клас, поддържащ методи set и get, приемащи/връщащи void *
2. темплейтен наследник на горния клас, в който се пази указател към T, но самите функции разбира се връщат пак void * (защото са в йерархия)
3. клас wrapper на горния, който не е темплейтен, но съдържа темплейтнати функции (т.е класа не е темплейт, но някой негови функции са), който държи един указател към базата и когато поискат нещо от него той прави dynamic_cast до наследник от искания тип, след което каства резултата към нещо човечко (т.е void * --> Т * --> Т). Хубавото е, че примерно set функцията сама разбира типа на шаблона (дедуцира го от аргумента) - затова няма смисъл да го пишем изрично (т.е set <int> (4). За get функцията обаче е задължителен (е няма пълно щастие :) ).

Хубавото тука е, че се извършва type checking от компилатора (при dynamic_cast), както и освобождаването на заделената памет от виртуалния деструктор на 2рия клас - т.е общо взето прехвърляме задълженията си към компилатора (както и според мен трябва да се прави).

п.с.: За да е малко по-полезно това чудо добавих и функция, която преобразува всеки тип до стринг (за да мога да печатам по лесно наред). Та наложи се да сложа и темплейт-специализация на тази вункция за типа int (т.е функцията to_str за едни типове да работи по един начин, за int да работи по друг). Тази функция, се третира като нормална, и за това трябва да стой в cpp файл, който се линква отделно (т.е не в h/hpp файла със шаблона, защото ако се инклудне от поне 2 места става мазало). По принцип темплейтите трябва да са написани в един h файл, защото компилатора не позволява да ги разделяме (extern template не е имплементирано почти никъде), но точно специализациите може (и трябва) да се пишат в отделен файл - иначе казва redefinition of ...


#include <cstdio>
#include <string>

class base {
public:
virtual void *get () = 0;
virtual void set (const void *) = 0;
virtual ~base () {}
};

template <typename T>
class derived : public base {
T *_data;
public:
derived () : _data (NULL) {}
virtual void *get () {
return static_cast <void *> (_data);
}
virtual void set (const void *data) {
delete _data;
_data = new T (*static_cast <const T *> (data));//copy constructor
}
virtual ~derived () {
delete _data;
}
};

class wrapper {
base *_p;
public:
wrapper () : _p (NULL) {}
template <typename T> T get () {
return *static_cast <T *> (dynamic_cast <derived <T> *> (_p)->get ());
}
template <typename T> bool is () {
return dynamic_cast <derived <T> *> (_p) != NULL;
}
template <typename T> void set (const T &nv) {
delete _p;
_p = new derived <T>;
_p->set (&nv);
}

};

int main () {

wrapper x;
x.set (5);
printf ("%d\n", x.get <int> ());

x.set (std::string ("alaabala"));
printf ("%s\n", x.get <std::string> ().c_str ());
printf ("%d %d\n", (int)(x.is <int> ()), (int)(x.is <std::string> ()));

return 0;
}

сряда, 30 април 2008 г.

pointer-to-member-function C++

(който не разбира заглавието - става дума за указатели към член-функции в класове на C++ - който и това не разбира да го прескочи :))

Ако искате да напишете код, който да работи с даден обект и да вика негова функция, обаче самата функция да може да се променя (т.е да напишете по-обща функция един вид) тогава е полезно да може да предавате така наречения указател-към-член-функция. В следните няколко парченца код ще опитам да покажа как точно става това граматически - на книжовен C++, защото лично аз имах малко проблеми докато докарам синтаксиса преди няколко дена.

Основният проблем е, че типът на една функция, която е в клас не е просто връщана-стойност (*) (аргуенти), защото тези функции получават един неявен указател this, който сочи към данните на обекта. Освен това този указател е от различен тип за всеки клас (т.е не може просто да кажем че това е member функция и да стане) - трябва като цитираме съответна функция да кажем и от кой клас е:
името-на-класа::името-на-функцията
.
Сега вече типа става:
връщана-стойност (име-на-клас::*име-на-функция) (аргументи) [const]

забележете, че ако функцията е const трябва да го кажем изрично - защото от това зависи типа на указателя this, който както казах неявно се предава.


class cls {
int mem_fun (const char *) const;
static void static_mem_fun (double, char);
};


Ако искаме да подадем mem_fun на някоя фунция (за да може тя да вика точно тази функция за даден обект - обекта отделно трябва да го дадем разбира се) трябва да я приемем по следния начин:
void mem_fun_user (const cls &c, int (cls::*f) (const char *) const) {
/*по този начин я викаме - обърнете внимание на скобите и звездата*/
/*ако c беше указтел към клас тогава викаме със '->*' */
int p = (c.*f) ("haha");
}

сега остава да покажа как се предава :)

void foo () {
cls c;
/*не слагайте допълнителни скоби, или аперсанта другаде - НЕ РАБОТИ - точно както съм го дал така :)*/
mem_fun_user (c, &cls::mem_fun);
}


Можете да подавате и указатели към оператори - просто трябва да ги изпишете (style="font-family:courier new;">&rational::operator +) - държат се като обикновенни функции (но се викат по интересно ;)).

При статичните функции е по-лесно - защото при тях няма този скрит указател this който казва на кой клас е. Т.е техния тип е като на обикновенна функция, а за да ги подадете пишете име-на-клас::име-на-функция както при член функциите

Ето една страничка с (може би) малко по-подробна информация по въпроса:
http://www.parashift.com/c++-faq-lite/pointers-to-members.html

понеделник, 28 април 2008 г.

Сефтето на блога

Ето го и първия пост :) Дано не зарежа и това начинание, както много други.
Като ми дойде музата пак ;)