NePo
Balandis 12, 2007

Metaprogramavimas

Kartais universitetas priverčia išbandyti dalykus, kurie atrodo keistai, apie kuriuos anksčiau nežinojai, bet pamiršta pasakyti svarbiausią dalyką - koks jų praktinis pritaikymas.
Niekada negalvojau, jog įmanoma padaryti amžiną kompiliavimo ciklą. Dabar žinau - aš talentingas.
Norėčiau papasakoti, savo įspūdžius, kaip man pavyko susipažinti su c++ šablonais ir metaprogramavimu.

- tai tokios programos rašymas, kuri generuoja kitą programą arba ja manipuliuoja. Programa, kuri kuriama arba kuria manipuliuojama, vadinama objektu ir pateikiama kaip duomenys. Programa, kuri atlieka manipuliacijas, vadinama metaprograma. Yra du meta programavimo būdai: homogeninis ir heterogeninis. Homogeninio programavimo metu objekto ir metaprogramos rašymui naudoja ta pati kalba, heterogeninio skirtingos.

Šį kartą aptarsiu tik homogeninį programavimo būdą. Pateiksiu pavyzdį c++ kalba, kurį man teko pasirašyti laboratorinio atlikimui ir paaiškinsiu kaip jis veikia. Bet prieš tai dar truputėlis teorijos:
Metaprogramoje nenaudojamos if-else sąlygos

if (salyga)
teiginys1;
else
teiginys;

jos yra keičiamos į:

template<bool C>
class _pavadinimas { };
class _pavadinimas<true> {
public:
static inline void funk()
{ teiginys1; } // true salyga
};
class _pavadinimas<false> {
public:
static inline void funk()
{ teiginys2; } // false sąlyga
};
// if-else sąlygos iškvietimas:
_pavadinimas<salyga>::funk();

switch-case taip pat yra keičiamas:

int i;
switch(i)
{
case reiksme1:
teiginys1;
break;
case reiksme2:
teiginys2;
break;
default:
teiginys3;
break;
}

į tokį kodą:

//Klasės aprašas
template<int I>
class _pavadinimas {
public:
static inline void funk()
{ teiginys3; }
};
class _pavadinimas<reiksme1> {
public:
static inline void funk()
{ teiginys1; }
};
class _pavadinimas<reiksme2> {
public:
static inline void funk()
{ teiginys2; }
};
// switch(i) sąlygos iškvietimas
_pavadinimas<I>::funk();

while ciklas

int i = N;
do {
teiginys;
} while (--i > 0);

keičiamas į:

// Klasės deklaracija
template<int I>
class _pavadinimas {
private:
enum { go = (I-1) != 0 };
public:
static inline void funk()
{
statement;
_pavadinimas<go ? (I-1) : 0>::funk();
// rekursijos kvietimas
}
};
class _pavadinimas<0> {
// ką daryti kai pasiekiama minimali reikšmė (nulis)? 
public:
static inline void f()
{ 
return 1;
}
};
// Ciklo kvietimas
_pavadinimas<N>::funk();

Beje, atkreipkite dėmesį, jog tai atvirkščias do-while ciklas, sukamas nuo didžiausios reikšmės iki mažiausios.

Tiek turėtų užtekti, kad galėtume spręsti pagrindinius uždavinius, nors prie pilnos kompanijos čia dar trūksta for ciklo.
Laboratorinio užduotis:
Apibendrinkite etaloninę funkciją sinh(x) pagal 2 nepriklausomus parametrus: duomenų tipą {float, double} ir eilutės narių skaičių {2..8}. Naudoti C++ templates.

float _sinh_2 (float x) {
return x * (1 + x*x/6);
}
 
double _sinh_3 (double x) {
return x * (1 + x*x/6) * (1 + x*x/20));
}
 
float _sinh_4 (float x) {
return x * (1 + x*x/6 * (1 + x*x/20 * (1 + x*x/42)));
}

Pagal duomenų tipą aš šį kartą neapibendrinsiu, tik pagal eilutės narių skaičių. Tiesą pasakius, užduotis gana kvaila, nes labai stipriai nukenčia tikslumas.

Pirmiausiai pasirašysime paprastą apibendrintą c++ funkciją, o po to ją versime į šabloną:

double sinh2(double x, int j)
{
double val = 1;
int k=1;
do
{k++;
val = 1 + x *x/((k-2+k)*(k-1+k))*val;
}while(k!=j);
return x * val;
}

Jeigu atidžiai pažvelgsite į kodą iš karto pastebėsite, kaip pagaminti amžiną ciklą. Tiems, kurie neatkreipė dėmesio pašnibždėsiu, kai j<2. Suprasite, kodėl nedėjau if-else sąlygos, kai pažvelgsite truputį aukščiau, kur aš aiškinau kaip ją paversti į šabloną.

Prieš tęsiant, reiktų pasakyti dar keletą dalykų. Mokindamasis kurti c++ šablonus pastebėjau, jog kaip parametrus visuomet paduoda tik sveikus skaičius, o pagal užduotį man reikia paduoti float. Norėdamas išspręsti savo užduotį nagrinėjau kitą kodą, kuriame realizuota sin(), tenai reikšmę x keisdavo pagal formule x=I*2*M_PI/N, tiesa, aš nesu tikras ar mano reikalinga pi, vis tik sinh() yra hiperbolis sinusas.

#include "stdafx.h"
#include <iostream>
#define _USE_MATH_DEFINES
// vėliau naudojam PI konstantą
#include <math.h>
using namespace std;
//---------------------------------------------------------------------------
template<int N, int I, int J, int K>
class SineSeries
{
 public:
  enum { go = (K != J) };
 
  static inline float accumulate()
  {
    return 1+(I*2*M_PI/N)*(I*2*M_PI/N)/((K-2+K)*(K-1+K)) *
    SineSeries<N*go,I*go,J*go,(K+1)*go>::accumulate();
  }
};

Pagrindinė funkcija, atlieka skaičiavimą ir iteraciją į save.

template<>
class SineSeries<0,0,0,0> 
{
 public:
  static inline float accumulate()
  { 
    return 1; 
  }
};

kai while ciklas pasiekia nulinę reikšmę iškviečiama šita funkcija.

template<int N, int I, int max>
class Sine
{
 public:
  static inline float sinh()
  {
    return (I*2*M_PI/N) * SineSeries<N,I,max,2>::accumulate();
  }
};

grąžinimas galutinis rezultatas.

void main()
{
   Sine <10, 20, 2> obj;
    cout << "rezultatai:" << obj.sinh(); 
}

Pagrindinė funkcija, kuri inicijuoja naudojimąsi mano sukurtu šablonu. Trečias skaičius negali būti mažesnis už du, nes turėsite amžiną ciklą ant kompiliavimo.

Tai tokia mano pažintis su šablonais ir metaprogramavimu. Pagrindinis privalumas - skaičiavimai atliekami žymiai greičiau. Tačiau darydamas šį laboratorinį susiduriau net su keliais trūkumas: 1) iteracijos kodas man niekada nebuvo aiškus; 2) daug kodo, su paprastu ciklu tik 10 eilučių.

Tikiuosi kam nors padės šis straipsnelis, nes informacijos nėra daug šita tema, o kartais reikia.

Dėkoju visiems, kurie padėjo pasidaryti man laboratorinį darbą.

Dar informacijos galima rasti čia:
Atviras Kodas Wiki

Panašūs straipsniai


“Metaprogramavimas” komentarų: 10

  1. Aras Pranckevičius

    Žudantis klausimas būtent šito pavyzdžio realizacijai: o kaip su metaprogramavimu realizuotu sinh() apskaičiuoti reikšmę, kai argumentas yra įvedamas vartotojo?

    Nes tavo pavyzdyje skaičiavimai nėra “atliekami greičiau”, jie iš viso nėra atliekami. Sine tiesiog pakeičiamas float konstanta ir tiek; pats galėtum ją su kalkuliatorium apskaičiuot ir įrašyt - ir kodo būtų žymiai mažiau ;)

    Užduotis, kaip suprantu, buvo realizuoti duomenų tipo ir iteracijų kiekio šablonizavimą. Maždaug taip:

    template
    T SinH( T argument ) { blah blah; };

    Jei su metaprogramavimu, tai turbūt tai atrodytų maždaug:

    template
    struct SinH {
    T Evaluate( T argument ) { blah blah; }
    };

    t.y. argumentas yra argumentas, kokia prasmė jį kišti į šablono parametrus?

  2. Aras Pranckevičius

    Oh my, wordpress visiškai suvalgo templeitų kodą tarp ‘mažiau’ ir ‘daugiau’ simbolių :(

  3. lfx

    liux straipsnis, pačiu laiku, kaip tik reiks parašyti programa su šablonais ;)

  4. Emilis

    Aš esu pasibaisėjęs tuo, ko moko universitetuose! :D

  5. asterisk

    Aras, kodą gali į paste.lt nupastinti ir čia tiesiog url įdėt ;) arba naudok <code>kodas</code>


  6. Už pirmą sakinį - 10+

  7. karaliuskunas

    ar galima su sia programa lauzti pvz: kokionors acounto koda ar pan.? Pvz as turiu Log-in ir man reikia pass.. :D

  8. m1tch5

    Su ja galima daug ka.Apie keyloggeri net nekalbu ;D.

  9. Adamsas

    yra klaida siame straipsnyje neatinka iso c++ standarto ir delto nesikopiliuoje ant vc2005 reiketu sita pakeisti
    template
    class _pavadinimas {
    public:
    static inline void funk()
    { teiginys3; }
    };
    class _pavadinimas {
    public:
    static inline void funk()
    { teiginys1; }
    };
    class _pavadinimas {
    public:
    static inline void funk()
    { teiginys2; }
    };
    pakeisti i
    template
    class _pavadinimas {
    public:
    static inline void funk()
    { teiginys3; }
    };
    template
    class _pavadinimas {
    public:
    static inline void funk()
    { teiginys1; }
    };
    template
    class _pavadinimas {
    public:
    static inline void funk()
    { teiginys2; }
    }
    eiline vc2005 nesamone.

  10. Patrikas

    Heh, seniau aktyviai su tuo zaisdavau, aplamai C++ galimybes ir perspektyva domino, cia viena naudinga mpl parasiau - saknies traukimas ir tikrinimas, gal kam pravers :)

    template struct is_sqrt {
    enum { ATS = is_sqrt::ATS };
    };

    template struct is_sqrt {
    enum { ATS = true };
    };

    template struct is_sqrt {
    enum { ATS = false };
    };

    template struct is_sqrt {
    enum { ATS = false };
    };

    int main() {
    // ptr::basetype obj(5);
    cout << is_sqrt::ATS;
    cin.get();
    return 0;
    };

Rašyti komentarą

Jūs privalote prisijungti jeigu norite rašyti komentarą.