2009-01-18 9 views
6

używam aplikacji w C++, który wykorzystuje specjalną funkcję dprintf do drukowania informacji, jest to przykład:informacje druku w „trybie testowym”, ale nie w „normalnym wykonaniu”

dprintf(verbose, "The value is: %d", i); 

Co robi jest kiedy definiuję verbose dla celów testowych, wtedy drukuję informacje i kiedy pracuję w normalnym wykonaniu, nie definiuję tego i nie widzę niepotrzebnych informacji na ekranie. Moje pytanie brzmi: jak mogę wykonać tę funkcję lub zrealizować ten sam pomysł ?.

Odpowiedz

14

staram się unikać var-Arg funkcje c stylu z dwóch głównych powodów:

  • nie są one typu bezpieczny, nie można używać operatorowi < <
  • Oni nie uznają kiedy podano zbyt mało lub wiele argumentów:

Zrobiłem sposób, który działa przy użyciu boost::fusion, który podaje argumenty w sposób bezpieczny dla typu. Powtarza on te argumenty, drukując je po napotkaniu %. Jeśli podano zbyt mało lub zbyt wiele argumentów, zostanie zgłoszony wyjątek.

Jest jeszcze jeden problem: makra Variadic nie są jeszcze standardem w C++. Tak, zrobiłem dwie wersje. Jeden, który działa z obecnym C++. Musisz go wywołać, używając:

dprintf("name: %, value: %\n", ("foo", 42)); 

Następnie. Druga wersja, za pomocą zmiennej liczbie argumentów makr, może być używany przez zdefiniowanie symbolu preprocesora, który pozwala pisać

dprintf("name: %, value: %\n", "foo", 42); 

Oto kod. Numer boost.fusion zawiera więcej szczegółów:

#include <boost/fusion/include/sequence.hpp> 
#include <boost/fusion/include/make_vector.hpp> 
#include <boost/fusion/include/next.hpp> 
#include <stdexcept> 
#include <iostream> 

template<typename IterS, typename IterSeqE> 
void print_vec(IterS b, IterS e, IterSeqE, IterSeqE) { 
    while(b != e) { 
     if(*b == '%') { 
      if(++b != e && *b == '%') { 
       std::cout << '%'; 
      } else { 
       throw std::invalid_argument("too many '%'"); 
      } 
     } else { 
      std::cout << *b; 
     } 
     ++b; 
    } 
} 

template<typename IterS, typename IterSeqB, typename IterSeqE> 
void print_vec(IterS b, IterS e, IterSeqB seqb, IterSeqE seqe) { 
    while(b != e) { 
     if(*b == '%') { 
      if(++b != e && *b == '%') { 
       std::cout << '%'; 
      } else { 
       std::cout << *seqb; 
       return print_vec(b, e, next(seqb), seqe); 
      } 
     } else { 
      std::cout << *b; 
     } 
     ++b; 
    } 
    throw std::invalid_argument("too few '%'"); 
} 

template<typename Seq> 
void print_vec(std::string const& msg, Seq const& seq) { 
    print_vec(msg.begin(), msg.end(), begin(seq), end(seq)); 
} 

#ifdef USE_VARIADIC_MACRO 
# ifdef DEBUG 
# define dprintf(format, ...) \ 
     print_vec(format, boost::fusion::make_vector(__VA_ARGS__)) 
# else 
# define dprintf(format, ...) 
# endif 
#else 
# ifdef DEBUG 
# define dprintf(format, args) \ 
     print_vec(format, boost::fusion::make_vector args) 
# else 
# define dprintf(format, args) 
# endif 
#endif 

// test, using the compatible version. 
int main() { 
    dprintf("hello %, i'm % years old\n", ("litb", 22)); 
} 
+1

Nie mogę mówić o innych kompilatorach, ale dla GCC, -Wformat i __attribute __ ((format)) są twoimi przyjaciółmi. To pozwala na API w stylu printf z całą dobrością sprawdzania typu operatora <<. – Tom

+1

Tak, jestem świadomy tych atrybutów. ale nie pozwoli ci przekazać własnych typów. na przykład możesz użyć listy. i chcesz przeciążyć operatora << dla twojej listy. z printf, musisz ponownie napisać tę samą pętlę i są one ograniczone do pewnych typów elementów, które rozumie printf. –

+0

Przeciążanie operatora << pozwala umieścić kod drukowania na stronie typu, który chcesz wydrukować, a ja wolę umieszczać kod drukowania na boku mojego kodu debugowania.więc, kiedy masz coś do wydrukowania, twoje bezpieczne dla Twojego rodzaju rozwiązanie dprintf już to rozumie. –

9
#ifdef DEBUG 
#define dprintf(format, ...) real_dprintf(format, __VA_ARGS__) 
#else 
#define dprintf 
#endif 

Tutaj real_dprintf() jest funkcją "prawdziwy", który jest wywoływany, a dprintf() jest tylko makro owijając połączenie.

+0

Zamierzałem zamieścić tę samą odpowiedź, więc zapisuję ją i przegrywam. Jest to jedyna sytuacja, w której używam makr preprocesora w kodzie C++. –

4

Rozwiązanie preprocesora będzie działało, ale może być denerwujące, że trzeba je przebudować, aby zmienić się z jednego na drugie. Często podejmuję decyzję w czasie wykonywania. I najpierw zadeklarować:

static void do_nothing(const char *fmt, ...) { (void)fmt; } 
extern void real_dprintf(const char *fmt, ...); 
void (*dprintf)(const char *fmt, ...) = do_nothing; 

Następnie w kodzie inicjującym mam

if (getenv("APPLICATION") && strstr(getenv("APPLICATION"), "dprintf")) 
    dprintf = real_dprintf; 

ten sposób można szybko zmieniać tryby zmieniając wartość zmiennej środowiskowej.

Powiązane problemy