Dodać komunikaty niestandardowe w asercie?

133

Czy istnieje sposób, aby dodać lub edytować wiadomość generowaną przez assert? Chciałbym użyć czegoś takiego

assert(a == b, "A must be equal to B");

Następnie kompilator dodaje linię , czas i tak dalej ...

Czy to możliwe?

Killrazor
źródło
Możesz zdefiniować makro, w ten sposób .
HelloGoodbye,

Odpowiedzi:

245

Hack, który widziałem, polega na użyciu &&operatora. Ponieważ wskaźnik „jest prawdziwy”, jeśli nie jest zerowy, możesz wykonać następujące czynności bez zmiany warunku:

assert(a == b && "A is not equal to B");

Ponieważ assertpokazuje stan, który się nie powiódł, wyświetli również Twoją wiadomość. Jeśli to nie wystarczy, możesz napisać własną myAssertfunkcję lub makro, które wyświetli, co chcesz.

zneak
źródło
27
Inną opcją jest odwrócenie operandów i użycie operatora przecinka. Potrzebujesz dodatkowych nawiasów, aby przecinek nie był traktowany jako separator między argumentami:assert(("A must be equal to B", a == b));
Keith Thompson,
3
Fajnie byłoby jednak móc wydrukować wartości zmiennych, jak w:assert(a == b && "A (" << A << ") is not equal to B (" << B << ")");
Frank
7
@Frank, printfzwraca wartość niezerową, jeśli coś wypisuje, więc możesz zrobić coś podobnego assert(a == b && printf("a (%i) is not equal to b (%i)", a, b)), chociaż w tym momencie prawdopodobnie powinieneś napisać własne opakowanie assert.
zneak
1
Zły kod! Nie rozumiem tego! Jeśli a == b jest fałszem, wyrażenie and powinno również mieć wartość fałsz, a zatem ciąg nie powinien być oceniany.
ragnarius
2
@TUIlover, nie tak działają literały ciągów C; są stałymi czasu kompilacji i ich użycie w tym kontekście jest banalnie zoptymalizowane. Nie ma żadnych kosztów działania.
zneak
48

Inną opcją jest odwrócenie operandów i użycie operatora przecinka. Potrzebujesz dodatkowych nawiasów, aby przecinek nie był traktowany jako separator między argumentami:

assert(("A must be equal to B", a == b));

(to zostało skopiowane z powyższych komentarzy, dla lepszej widoczności)

Andrei Bozantan
źródło
3
Jest to świetne podejście, z jednym drobnym problemem, wyświetli "ostrzeżenie: lewy operand operatora przecinka nie ma wpływu" po skompilowaniu w g ++ z `-Wunused-value
v010dya
1
lub z makrem: #ifndef m_assert #define m_assert (expr, msg) assert ((msg, expr)) #endif
Szymon Marczak
Korzystanie z opakowania makr pozwala uniknąć ostrzeżenia gcc:#define m_assert(expr, msg) assert(( (void)(msg), (expr) ))
Jander
26

Oto moja wersja makra assert, które akceptuje wiadomość i wyświetla wszystko w przejrzysty sposób:

#include <iostream>

#ifndef NDEBUG
#   define M_Assert(Expr, Msg) \
    __M_Assert(#Expr, Expr, __FILE__, __LINE__, Msg)
#else
#   define M_Assert(Expr, Msg) ;
#endif

void __M_Assert(const char* expr_str, bool expr, const char* file, int line, const char* msg)
{
    if (!expr)
    {
        std::cerr << "Assert failed:\t" << msg << "\n"
            << "Expected:\t" << expr_str << "\n"
            << "Source:\t\t" << file << ", line " << line << "\n";
        abort();
    }
}

Teraz możesz tego użyć

M_Assert(ptr != nullptr, "MyFunction: requires non-null argument");

A w przypadku niepowodzenia otrzymasz taki komunikat:

Assert nie powiodło się: MyFunction: wymaga argumentu innego niż null

Oczekiwano: ptr! = Nullptr

Źródło: C: \ MyProject \ src.cpp, wiersz 22

Ładnie i czysto, możesz użyć go w swoim kodzie =)

Eugene Magdalits
źródło
Niezłe. Bardzo przydatne
Killrazor
Jestem trochę zdezorientowany. Czy #Expr jest traktowane jako ciąg do bezpośredniego podstawienia? Jaka jest różnica między #Expr i Expr?
Minh Tran
@MinhTran Załóżmy, że warunek potwierdzenia to x == y. Następnie Expr rozwinie się do if( !(x == y))i tutaj sprawdzany jest warunek, a #Expr rozwinie się do literału ciągu "x == y", który następnie umieścimy w komunikacie o błędzie.
Eugene Magdalits,
Niestety to rozwiązanie powoduje niezdefiniowane zachowanie z powodu używania zarezerwowanych identyfikatorów.
Pamiętaj o Monice,
19
BOOST_ASSERT_MSG(expre, msg)

http://www.boost.org/doc/libs/1_51_0/libs/utility/assert.html

Możesz użyć tego bezpośrednio lub skopiować kod Boost. Zwróć również uwagę, że potwierdzenie Boost jest tylko nagłówkiem, więc możesz po prostu pobrać ten pojedynczy plik, jeśli nie chcesz instalować całego Boost.

Zero
źródło
@Jichao, co masz na myśli mówiąc o implementacji interfejsu assert?
Tarc
8

Ponieważ odpowiedź zneak nieco zawija kod, lepszym podejściem jest po prostu skomentowanie tekstu ciągu, o którym mówisz. to znaczy.:

assert(a == b); // A must be equal to B

Ponieważ czytelnik błędu assert i tak odszuka plik i wiersz z komunikatu o błędzie, zobaczy tutaj pełne wyjaśnienie.

Ponieważ pod koniec dnia:

assert(number_of_frames != 0); // Has frames to update

czyta lepiej niż to:

assert(number_of_frames != 0 && "Has frames to update");

pod względem analizy kodu przez człowieka, tj. czytelność. Nie jest to też hack językowy.

metamorfoza
źródło
1
„Ponieważ czytelnik potwierdzenia błędu i tak przejrzy plik i wiersz w komunikacie o błędzie” - tylko wtedy, gdy jest sumienny.
Jason S
Tylko jeśli chcą naprawić błąd, który masz na myśli ... co za głupi komentarz
metamorfoza
2
Nie. Im łatwiej sprawisz, że ludzie zobaczą problem, tym większe prawdopodobieństwo, że podejmą działania.
Jason S
wzruszenie ramionami Nie zgadzam się.
metamorfoza
2

assert jest kombinacją makra / funkcji. można zdefiniować własne makro / funkcję, za pomocą __FILE__, __BASE_FILE__, __LINE__itp, z własnej funkcji, które ma niestandardowy komunikat

Merlyn Morgan-Graham
źródło
0

Możesz także po prostu napisać własną niestandardową funkcję assert. Bardzo prosty przykład:

bool print_if_false(const bool assertion, const char* msg) {
    if(!assertion) {
        // endl to flush
        std::cout << msg << std::endl;
    }
    return assertion;
}

int main()
{
    int i = 0;
    int j = 1;
    assert(print_if_false(i == j, "i and j should be equal"));
    return 0;
}

bawić się kodem.

Twierdzenie brzmi Assertion print_if_false(i == j, "i and j should be equal").

Użytkownik12547645
źródło
-6

W przypadku vc dodaj następujący kod w assert.h,

#define assert2(_Expression, _Msg) (void)( (!!(_Expression)) || (_wassert(_CRT_WIDE(#_Msg), _CRT_WIDE(__FILE__), __LINE__), 0) )
Jichao
źródło
11
Modyfikowanie nagłówków kompilatora to zły pomysł.
Ross Ridge