macro z wskaznikiem do funkcji

0

mam taki schematyczny kod, ktory powtarza mi sie kilkanascie razy:
while
{
try
{
...
funkcja
...
}
catch
{
...
}
}
chcialbym napisac macro, do ktorego podam funkcje i jej parametry
problemem jest to ze te funkcje sa bardzo rozne, tzn zarowno jesli chodzi o zwracany typ, jak i ilosc i typy parametrow
ale jakos nie wiem jak to ugryzc

0

To co próbujesz robić jest raczej głupim pomysłem.

Ale skoro się upierasz ;-)
Do makra możesz dać cokolwiek jako argument np.

#define MAKRO(f) f;

i potem w kodzie:

MAKRO(foo(1,2,"hello world'));

To jak już wiesz jak przekazać tę "dowolną" funkcję to chyba problem rozwiązany?

0

dzieki za pomoc
a mozesz napisac czemu to zly pomysl?
chodzi mi o to zeby nie powielac niepotrzebnie kodu, ktory jeszcze moze ulegac modyfikacja,
wiec dzieki temu zamiast zmieniac w 15 miejscach kod bede mogl zmienic tylko w jednym

jesli masz pomysl na lepsze rozwiazanie, to ja chetnie poczytam

0

Jest kilka powodów

Nie wiedziałeś jak to napisać. Pytanie, czy ktokolwiek będzie umiał to zrozumieć (wliczając autora), czytając kod ;-)

Makra są potężne, ale bardzo niebezpieczne. Wystarczy, że jako argument przekażesz x++, a w makrze parametr występuje więcej niż raz.

Rozumiem, że chcesz wprowadzić w życie zasadę DRY. Ale IMO trochę przeginasz. Równie dobrze można by chcieć zakazać powtórzeń innych konstrukcji językowych (np. definicja klasy, albo pętla). DRY to zasada, a nie prawo. Można ją łamać, tylko trzeba wiedzieć jakie są skutki.

Rozumiem, że problem polega na tym, że masz pewną liczbę funkcji. Wszystkie mogą zgłaszać wyjątki. Co więcej zbiór możliwych wyjątków dla każdej z funkcji jest taki sam. Dodatkowo, dla danego wyjątku e dla każdej funkcji obsługa e jest taka sama.

Jeżeli obsługa pewnego błędu jest taka sama dla kilku funkcji, to warto tę obsługę zamknąć w funkcji (np. obsługa_błędu_1()). Wtedy potrzebujesz zawsze napisać:

try {
 foo();
}
catch(MyException& e) {
 obsługa_błędu_1(e);
}

i nie ma powtarzającego się kodu.

To jest proste rozwiązanie, ale kod dalej się powtarza (trzeba pisać try - catch).

Można pójść dalej i skorzystać z metody szablonowej (ale trzeba się zastanowić czy to jest potrzebne). Zamiast funkcji robisz funktory.

Najpierw metodę szablonową:

class Foo {
public:
    void operator()() {
        try {
           wywolanie_funkcji();
        }
        catch(E1& e) {
          // obsługa e1
        }   
        catch(E2& e) {
         // obsługa e2
        } 
    }

protected:
    void wywolanie_funkcji() = 0; 
};

A potem implementacja poszczególnych funkcji (funktorów).
np.

class Foo1 : public Foo {
public:
    explicit Foo1(int param1, double param2)  : m_param1(param1), m_param2(param2) {
    }

    void wywolanie_funkcji() {
       // tu ciało funkcji foo2
       // np.
       if (m_param1 == 0)
           throw Ex1;
       if (m_param2 < 3.0)
           throw Ex2;
    }

private:
    int m_param1;
    double m_param2;
};

Wykorzystanie:

Foo1 f = Foo1(3, 15.0);
f();

Pozostałe metody definiujesz tak samo. Można pójść jeszcze dalej :-) i skorzystać z boost::function / boost::lambda, aby zaoszczędzić na pisaniu. Pytanie tylko czy to wszystko jest potrzebne.

// edit:
wykorzystać można też tak:

Foo1(3,15.0)(); // tworząc obiekt chwilowy

// edit2:
kolorowanie kodu

0

Może coś w tym stylu:

#define START_CATCH_WHILE( warunek ) while( warunek ) \
{ \
  try \
  {

#define END_CATCH_WHILE( handle ) }  \
  catch( std::bad_cast& e ) \
  { \
    handle( e ); \
  } \
}

// Zastosowanie
i = -6
START_CATCH_WHILE( i<0 )
       obiekt->CosTam( 3, "sdfs", i++ );
END_CATCH_WHILE( MetodaDlaHandleCatch );

znak \ na końcu linii jest istotny!

Jeszcze inne rozwiązanie (bezpieczniejsze) to zastosowanie szablonów i wyrażeń lambda, ale do tego musimy wiedzieć co dokładnie co chcesz zrobić.

0

te metody szablonowe wygladaja ciekawie, musze zglebic temat :)

moze konkretnie o przypdaku:
mam aplikacje klient-serwer, serwer udostepnia pewne funkcje, ktore uruchamiaja funkcje z warstwy biznesowej
problem polega na tym ze na bazie danych tworza sie deadlock, system bazodanowy (sql server 2005) radzi sobie z tym, sygnalizujac to bledem 1205 (od razu mowie ze nie jestem w stanie wyeliminowac deadlock'ow, wymagaloby to zbyt duzej ingerencji w architekture kilku bibliotek biznesowych, ktorych nie moge ruszyc, a musze stosowac), wiec aby nie zwracac klientowi bledu, staram sie powtorzyc transakcje, takich metod mam 15 w ktorych takie podejscie jest stosowane, wiec nie chodzi tu tylko o obsluge bledu w catch
dokladny schemat wyglada tak:

TranMenager tran();
bool isOK = false;
int repeatNum = 0;
while (!isOK && repeatNum < MAX_REPEATS)
{
  repeatNum++;
  try
  {
    tran.Start();
    JakasMojaFunkcjaZLogikiBiznesowej(/*rozne parametry*/);
    isOK = true;
  }
  catch (MyException *e)
  {
    if (e->ErrCode == SQL_1205)
    {
      delete e;
      isOK = false;
      tran.Rollback();
      int sleepTime = MIN_SLEEP_TIME + (int)(MAX_SLEEP_TIME * rand() / (RAND_MAX + 1.0));
      Sleep(sleepTime);
    }
    else
      throw e;
  }
}
if (isOK == false) RaiseError_Unknown();
tran.Commit();
0

co jest pod /rozne parametry/?
parametry z poza zakresu kodu, który podałeś, czy są to parametry, które zmieniają się przy każdym przejściu pętli?

Metod jest kilka. Można to zrobić nawet prostym dziedziczeniem i metodą wirtualną. Jak teraz widzę co i jak to stwierdzam, że masz banalny problem.
Wystarczy zamknąć JakasMojaFunkcjaZLogikiBiznesowej(/rozne parametry/); w jakieś metodzie i ją podmieniać.

0

Ja bym skorzystał z metody szablonowej (to jest klasyczny przypadek, kiedy się z tego korzysta).

Dzięki temu będziesz musiał zdefiniować tylko wywołania funkcje logiki biznesowej.

Idea jest taka: Definiujesz klasę Transakcja. Ma ona metodę run (czy operator() jak w moim przykładzie). Ta metoda robi wszystko co napisałeś powyżej + wywołuje metodę do() (czy coś takiego). Metoda do jest definiowana w podklasach. Argumenty zadaje się tworząc obiekt danej klasy (w konstruktorze).

Dla każdej funkcji logiki definiujesz jedną klasę. Po kłopocie. Myślę, że nie ma co się zastanawiać (szkoda czasu). Kod będzie bardzo rozszerzalny (definiujesz tylko klasę dla funkcji) i mało podatny na błędy.

BTW. Jeżeli wyłapujesz wyjątek przez
catch (MyException *e)
to musi być on rzucany przez
throw new MyException
w przeciwnym wypadku nie zostanie złapany (typ nie będzie się zgadzał)

zazwyczaj robi się
catch (MyException& e)
i zgłasza
throw MyException();

//edit: poprawiłem orta ;-)

0
// bazowa klasa opakowująca:
class Foo
{
public:
	// Metoda wywołujaca funkcję z logiki biznesowej
	virtual void operator()( int parametr ) = 0;
};

// przykładowa klasa opakowująca:
class Foo2 : public Foo
{
	double extraParametr;
public:
	Foo2( double dodatek ) : extraParametr( dodatek )
	{}
	
	void operator()( int parametr )
	{
	JakasMojaFunkcjaZLogikiBiznesowej( parametr, extraParametr );
	}
};

void TwojKod( Foo& ObiektOpakowujacy )
	{
	TranMenager tran();
	bool isOK = false;
	int repeatNum = 0;
	while (!isOK && repeatNum < MAX_REPEATS)
		{
		repeatNum++;
		try
			{
			tran.Start();
			ObiektOpakowujacy( repeatNum );
			isOK = true;
			}
		catch (MyException *e)
			{
			if (e->ErrCode == SQL_1205)
				{
				delete e;
				isOK = false;
				tran.Rollback();
				int sleepTime = MIN_SLEEP_TIME + (int)(MAX_SLEEP_TIME * rand() / (RAND_MAX + 1.0));
				Sleep(sleepTime);
				}
			else
				throw e;
			}
		}
	if (isOK == false) 
		RaiseError_Unknown();
	tran.Commit();
	}

// przykładowe wywołanie:	
TwojKod( Foo2( 0.2324) );
lmmilewski napisał(a)

BTW. Jeżeli wyłapujesz wyjątek przez
catch (MyException *e)
to musi być on rzucany przez
throw new MyException
w przeciwnym wypadku nie zostanie złapany (typ nie będzie się zgadzał)

zazwyczaj robi się
catch (MyException& e)
i zgłasza
throw MyException();
Jeśli jest to wyjątek z JakasMojaFunkcjaZLogikiBiznesowej to raczej nie ma wyboru.

@lmmilewski napisałeś "z metody szablonowej", a raczej miałeś na myśli obiekt funkcyjny.

0

Dzieki Panowie.
Wyglada super.

0

@lmmilewski napisałeś "z metody szablonowej", a raczej miałeś na myśli obiekt funkcyjny.

Napisałem to co miałem na myśli. "Metoda szablonowa" (template method) to nazwa operacyjnego wzorca projektowego. Nie można tego mylić z szablonami (templates) języka C++.

Twój kod też wygląda dobrze. IMHO ma jednak niepotrzebną wadę - jest bardzo podatny na błąd. Programista jest zmuszony pamiętać o sposobie wywołania.

0
lmmilewski napisał(a)

jest bardzo podatny na błąd. Programista jest zmuszony pamiętać o sposobie wywołania.

bardzo podatny? co mozna zepsuc w linii "TwojKod( Foo2( 0.2324) );" ?
a1) jelsi sie zapomni "0.2324" => TwojKod( Foo2( ) ); ---- kompilator zglosi blad
a2) jelsi sie zapomni "Foo2(..)" => TwojKod( 0.2324 ); ---- kompilator zglosi blad
a3) jesli zapomni opakowania "TwojKod(..) => "Foo2( 0.2324);" ---- skompiluje sie, wlasciwa operacja nigdy sie nie wykona

Twoj przyklad template method brzmialby np: Foo2().run(0.2324);
b1) jelsi sie zapomni "0.2324" => Foo2().run(); ----kompilator zglosi blad
b2) jelsi sie zapomni "Foo2()" => run(0.2324); ---- kompilator zglosi blad
b3) jesli zapomni "run(...)" => Foo2(); ---- skompiluje sie, wlasciwa operacja nigdy sie nie wykona

podatnosc na bledy jest IMHO tutaj dokladnie taka sama. a3/b3 sa rownowazne i rownie (malo) prawdopodobne

moim zdaniem, wzorzec z funktorami jest w tym przypadku lepszy, poniewaz:

  • nie tworzy sztucznego drzewa klas [dziedziczenie, chociaz wygodne, nie jest po to by uwspolniac implementacje!]
  • oddziela implementacje 'opakowania' od 'wlasciwej rzeczy'
    --adziekitemu-- pozwala uzyc 'wlasciwej rzeczy' bez opakowania [ Foo2(0.2324)(); ]
    ----adziekitemu-- pozwala uzyc innego opakowania bez duplikowania kodu 'wlasciwej rzeczy'
0

Skoro już muszę się tłumaczyć z tego co napisałem, to może zacznę od tego, że miałem dwa pomysły na rozwiązanie problemu. Pierwszy to metoda szablonowa. Drugi to metoda szablonowa. Różnica polega na tym, że w pierwszym przypadku osiągnięte byłoby to przez dziedziczenie, w drugim przez zawieranie.

Zdecydowałem się na pierwszy sposób bo moim zdaniem lepiej pasuje do zadania. Jeżeli przez A rozumiemy klasę po której w pierwszym przypadku dziedziczą inne, to drugi sposób różni się tym, że zamiast dziedziczenia po A, odpowiednią klasę (części algorytmu) przekazuje się przez konstruktor A (jako parametr).

Skoro zwróciłeś uwagę, że można zapomnieć wywołać run (co przeoczyłem) to drugi sposób wydaje się być podobny do tego zaproponowanego przez Marka. Jednak nie są to sposoby identyczne.

Dlaczego dziedziczenie a nie zawieranie? Bo taka była specyfikacja. Jest n algorytmów, różnią się tylko jednym wywołaniem funkcji. Trzeba je zapisać w ten sposób, aby jak najmniej kodować i dać możliwość łatwego dodawania nowych algorytmów, bez modyfikacji istniejącego kodu.

Skoro ma być możliwość łatwego dodawania nowych algorytmów w obrębie zadanej rodziny, to trzeba położyć nacisk właśnie na ten aspekt. Dlaczego dziedziczenie? Bo dzięki temu nowa podklasa (algorytm) ma możliwość dużej ingerencji w działanie algorytmu podstawowego. Nie jest np. problemem dodanie obsługi nowego wyjątku tylko w tym przypadku, albo wywołania dwóch funkcji, zamiast jednej. Można łatwo zmienić tylko implementację cofania w przypadku nieudanej transakcji. Wystarczy, że klasa A będzie zaimplementowana z głową (czyli np. definiuje kilka metod chronionych dla każdej z części algorytmu, a główna metoda tylko je wywołuje). Bardzo istotne jest to, że interfejs nie ulega zmianie. Tzn. tworzę nowy algorytm, ale nikt tego nie zauważa (w szczególności nie muszę wiedzieć dla każdego algorytmu, jakie opakowanie jest dla niego dobre, a jakie nie).

Drugi sposób daje jeszcze coś - mniejsze sprzężenie między konkretnym elementem algorytmu, a całą resztą. Dzięki temu, ten element daje się wykorzystać w innych kontekstach. Nie zaproponowałem tego rozwiązania, bo sprzężenie jest w tym przypadku korzystne - pozwala konkretnym elementom algorytmu bardziej ingerować w całość, przez co można lepiej dopasować sam algorytm (w przezroczysty dla użytkownika sposób).

quetzalcoatl napisał(a)

bardzo podatny? co mozna zepsuc w linii "TwojKod( Foo2( 0.2324) );" ?

a3) jesli zapomni opakowania "TwojKod(..) => "Foo2( 0.2324);" ---- skompiluje sie, wlasciwa operacja nigdy sie nie wykona
(...)
b3) jesli zapomni "run(...)" => Foo2(); ---- skompiluje sie, wlasciwa operacja nigdy sie nie wykona

Tak jak wspomniałeś, można napisać Foo2(0.2324) zamiast TwojKod(Foo2(0.2324)).

Przeoczyłem tę możliwość, że programista zapomni wywołać run. Warto zauważyć, że kompilator powinien wystosować ostrzeżenie o nieużywanej zmiennej (w obu przypadkach).

IMO jednak błąd b3 jest znacznie mniej prawdopodobny. Foo2() używa się tylko raz (!), potem tylko się wywołuje run (chociaż ja bym się skłaniał do przeciążenia operatora wywołania funkcji).

quetzalcoatl napisał(a)

[dziedziczenie, chociaz wygodne, nie jest po to by uwspolniac implementacje!]

Dziedziczenie ma dwa oblicza. W odniesieniu do interfejsów oraz w odniesieniu do klas abstrakcyjnych. W pierwszym przypadku sprawa jest oczywista. W drugim zaproponowana zasada uniemożliwia skorzystanie z jakiegokolwiek dziedziczenia (raczej nikt nie tworzy klas po to, aby potomne dziedziczyły tylko pola, co samo w sobie jest niemądre).

Dziedziczenie zostało wręcz stworzone (oczywiście nie tylko po to), żeby ograniczać powtórzenia (czyli uwspólniać implementacje).

BTW chyba nie myślałeś, że ja jestem aż tak mądry, żeby wymyślić takie ogólne i ładne rozwiązanie :-) Pochodzi ono z książki "Design Patterns: Elements of Reusable Object-Oriented Software" by GoF.

Jeszcze jeden komentarz do kodu Marka. Nie napisałem, że on jest zły! Tylko podatny na błędy. Jest tak, bo nie daje żadnych korzyści w porównaniu z metodą szablonową (tej z zawieraniem) (a może jednak?), a odbiera możliwość oddzielenia utworzenia funkcji od jej wywołania, przez co w pewnych przypadkach powtarza się kod, co może być przyczyną błędu.

Faktycznie przesadziłem z tym bardzo podatne na błędy.

Jasne - można się kłócić, które rozwiązanie jest lepsze. Napisałem jak ja zrozumiałem zadanie postawione przez autora wątku. Myślę, że przy takim rozumieniu rozwiązanie z metodą szablonową (w oparciu o dziedziczenie) jest najlepszym rozwiązaniem (poza jakąś tam własną analizą utwierdza mnie też w tym fakt, że jest to książkowy przykład dla tego wzorca).

0

BTW - wiadomo :)

krotko - zgadzam sie.

w szczegole -- w zasadzie, powiedzielismy to samo, tylko ja sie troche 'skompresowalem' do punktow... i obaj bronimyswoich racji tymi samymi argumentami. roznica polega na innym zalozeniu: zalozylem ze korzystne bedzie jak najmniejsze sprzezenie..

co do dziedziczenie-a-uwspolniania implementacji.. hm. zgodze sie, ze wlasnie jest to klasyczny przypadek wykorzystania dziedziczenia wlasnie do uwspolnienia implementacji.. mimo wszystko jednak wydaje mi sie to w ogolnosci moze nie tyle bledem, co niedociagnieciem designu.. por. z kompozycja: [pardon za angielski]

  • inheritance:
    class A {virtcall()=0} implements reliability as do try{ virtcall() } catch{} while(!exception)
    class B : A implements virtcall() { send "hello" to Alice, receive ack, throw if no ack }
    class C : A implements virtcall() { send "bye" to Alice, receive ack, throw if no ack }

  • composition
    class A<T> { T x; } implements reliability as do try{ x.do() } catch{} while(!exception)
    class B implements do as { send "hello" to Alice, receive ack, throw if no ack }
    class C implements do as { send "bye" to Alice, receive ack, throw if no ack }

*) oba sa rownowazne pod katem dzialania.
*) zgodze sie, ze 1)inheritance (troche) lepiej zabezpiecza przed bledami klasy 'duza literowka'
:) gdybym byl upierdliwy, zwrocilbym uwage ze virtcall jest wolnieszy od call. dodaje to zartem:)
!) sadze ze nie sa rownowazne w kontekscie rozwoju kodu/systemu

co prawda, w obu prosto jest dodac nowa implementacje czynnosci czyli dorzucic jakas klase X ktora np. bedzie wysylac "bugoff" do Boba, jednak w przypadku 1)inheritance ciezko jest dostarczyc innego sposobu dostarczania 'reliability'. nalezaloby zduplikowac kod klas B i C

ale.. dostarczanie extensibility na sile jest hm zwykle wytaczaniem dziala na komara.
zgadzam sie wiec ze tutaj 1) inheritance bedzie lepsze

0

sadze ze nie sa rownowazne w kontekscie rozwoju kodu/systemu

Też tak myślę. Może ktoś to będzie jeszcze czytał, więc powtórzę jeszcze, że żadne z tych rozwiązań nie jest lepsze pod tym względem. Po prostu są różne.

Kompozycja pozwala nawet na dynamiczną zmianę jakiejś części składowej. Co więcej, tak jak piszesz, można wymieniać "nakładkę", bo poszczególne części zależą tylko od jej interfejsu.

Dziedziczenie nie pozwala na żadne z tych, bo wprowadza ścisłą zależność między częściami algorytmu i ogólnym schematem (tak ścisłą, że stają się jednym obiektem). Ale to daje też plusy. Dziedzicząc można bardzo mocno ingerować w ogólny schemat, co nie jest osiągalne w przypadku kompozycji (przy której ograniczeniem jest interfejs, czyli główna zaleta tamtego rozwiązania).

To tak w ramach podsumowania, bo nic nowego nie napisałem :-)

//q: tosmy sobie poopowiadali :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1