Assert with side effects

Started by
9 comments, last by DevFred 14 years, 7 months ago
I learned about asserts recently and decided to start using them. Then I came to a point where I wanted to assert the value of a function that had side effects. I realized that the program would stop working if NDEBUG was defined. So I decided to store the function value in a temporary variable and assert that instead. But that makes the code messy, so I thought I should put it in a function, and then put the function in a header.

#ifndef EVALANDASSERT_H
#define EVALANDASSERT_H
#include <cassert>
void EvalAndAssert(int arg)
{
    assert(arg);
}
#endif



But now the information about the location of the assert that failed won't be particularly helpful because it will always be in that header file. Any ideas? Am I missing a better way to do things? P.S. Also, do you think it would be a good idea to give my include guards random names? E.g.

#ifndef EVALANDASSERT_H__EIYV4KUFGXMPFDPRCOS266LH7YWIVUNR

I trust exceptions about as far as I can throw them.
Advertisement
Is the purpose of the 'random' include guard name just to reduce the chance of a symbol clash?

If so, I wouldn't use a random name, but rather a name that's meaningful but will still have a reasonably low probability of clashing with another symbol (e.g. the header name prefixed with the name of the game/application, your initials, a company name, etc.).

I'm not sure I understand the point of your EvalAndAssert() function - it just looks like a wrapper for assert to me. (And shouldn't that be bool rather than int?)

Can you show an example of how the function would be used?
It's usually called "verify". And the easiest thing to do is just declare it as a macro alongside assert, and where release mode has #define assert(x) /*nothing*/, it also has #define verify(x) (x).
Quote:Original post by jyk
I'm not sure I understand the point of your EvalAndAssert() function - it just looks like a wrapper for assert to me.

The point is to force evaluation of the clause, even in release mode. The function body may be a no-op, but the process of calling it isn't.
Quote:The point is to force evaluation of the clause, even in release mode.
Ah, I see.
Quote:Original post by Storyyeller
*** Source Snippet Removed ***

But now the information about the location of the assert that failed won't be particularly helpful because it will always be in that header file. Any ideas?
Am I missing a better way to do things?


You want to setup your function like this:

#define EvalAndAssert(x) EvalAndAssertFunction(x, __FILE__, __FUNCTION__, __LINE__)void EvalAndAssertFunction(int arg, const char * file, const char * function, int line){    // Now, you have access to the file, function, and line //    assert(arg);}


So in your code:
int x = DoSomeWork();
EvalAndAssert(x); // Preprocessor macros kick in and pass the data to your function

You can use similar constructs for generic logging (only works on VS 2005 and above due to __VA_ARGS__):

log.h
#pragma once#ifndef LOG_H_#define LOG_H_//-------------------------------------------------------------------------const int LOG_MODE_ERROR		= 0;const int LOG_MODE_WARNING		= 1;const int LOG_MODE_INFORMATION	= 2;const int LOG_MODE_DEBUG		= 3;//-------------------------------------------------------------------------#define LOG(mode, text, ...) Log(mode, OnLog, __FILE__, __FUNCTION__, __LINE__, text, __VA_ARGS__)//-------------------------------------------------------------------------typedef void (*logFunc)(int mode, const char * file, const char * function, int line, const char * text);//-------------------------------------------------------------------------void Log(int mode, logFunc func, const char * file, const char * function, int line, const char * text, ...);//-------------------------------------------------------------------------extern void OnLog(int mode, const char * file, const char * function, int line, const char * text);//-------------------------------------------------------------------------#endif


log.cpp
#include "log.h"#ifndef _INC_STDIO	#include <stdio.h>#endif#ifndef _INC_STDARG	#include <stdarg.h>#endif//-------------------------------------------------------------------------void Log(int mode, logFunc func, const char * file, const char * function, int line, const char * text, ...){	char buffer[8192] = {0};	va_list args;	va_start(args, text);	vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, text, args);	va_end(args);	func(mode, file, function, line, buffer);}//-------------------------------------------------------------------------


The reason I showed the Log example was in case you wanted to add custom information, for example:
int x = DoSomeWork(varC);
EvalAndAssert(x, "DoSomeWork(%i) returned %i.", varC, x);

You can switch up the style of how it all works, that's just one example of it from the code I grabbed it from.

Oh, and of course, you have to be wary of evil macros! If you are careful though, it should be alright.
Would something like this work?

#ifdef assert(x)    #define EvalAndAssert(x) assert(x)#else    #define EvalAndAssert(x) (x)#endif
I trust exceptions about as far as I can throw them.
Quote:Original post by Storyyeller
Would something like this work?

*** Source Snippet Removed ***


No, that's not setup correctly. Rather than trying to force asserts to work for you, I think you need to rethink what kind of error handling you want to have. In other words, don't use asserts just because they are there, there are a lot of disadvantages to using them as well (of which you are already seeing some of them).

What are you trying to do, error handling wise? Perhaps another style of error handling logic would be more suitable for your needs.
Well I read that you are supposed to use asserts to verify things that are supposed to always be true, which is more or less what I've been using them for.
I've already run into several impossible conditions which were caught by asserts, which reduces the trial and error of debugging significantly.

As for what I'm trying to do, here's my current code

I'm trying to create a game where people alternatately put pieces on a board and the first person to make a square loses, but I haven't gotten very far.
The part that originally prompted my inquiry was on line 171 of the cpp file.

board.h
#ifndef SQPHOBIABOARD_H#define SQPHOBIABOARD_H#include <vector>#include <cstdlib>#include <string>typedef unsigned long u32;typedef std::string str;struct SqMove{    u32 x;    u32 y;    u32 type;    SqMove( u32 nx, u32 ny,  u32 nt) :        x(nx), y(ny), type(nt)        {}};class SqBoard{    const static u32 NUMTYPES = 2;    u32 height;    u32 width;    std::vector<bool> marks;    std::vector<bool> safety;    bool IsValid( u32 x, u32 y) const    {   return (x>=0) and (x<width) and (y>=0) and (y<height);  }    u32 ToIndex( u32 x, u32 y) const    {   return x + y * width;   }    bool IsSafe(  u32 x, u32 y, u32 type) const;    void SetSafety(  u32 x, u32 y, u32 type, bool val);    bool IsMarked(  u32 x, u32 y, u32 type) const;    void SetMark(  u32 x, u32 y, u32 type, bool val);    u32 GetRandom() const    {   return rand() ^ (rand() << 16); }    public:    SqBoard( u32 h, u32 w);    bool UpdateWithMove( u32 x, u32 y, u32 type); //return val is whether you lost or not    bool UpdateWithMove( SqMove newmove); //return val is whether you lost or not    u32 MakeRandomMoves( u32 count); //returns number of moves successfully made    str ToString() const;     u32 Height() const    {return height;}    u32 Width() const    {return width;}};#endif


board.cpp
#include <cassert>#include "board.h"#include <iostream>void EvalAndAssert( int arg ){    assert(arg);}//////////////////////////////////////////////////////////bool SqBoard::IsSafe( u32 x, u32 y, u32 type) const{    assert( IsValid(x,y)    );    assert(type<NUMTYPES);    return safety.at( type + ToIndex(x,y) * NUMTYPES);}void SqBoard::SetSafety( u32 x, u32 y, u32 type, bool val){    assert( IsValid(x,y)    );    assert(type<NUMTYPES);    safety.at( type + ToIndex(x,y) * NUMTYPES) = val;}bool SqBoard::IsMarked( u32 x, u32 y, u32 type) const{    assert( IsValid(x,y)    );    assert(type<NUMTYPES);    return marks.at( type + ToIndex(x,y) * NUMTYPES);}void SqBoard::SetMark( u32 x, u32 y, u32 type, bool val){    assert( IsValid(x,y)    );    assert(type<NUMTYPES);    marks.at( type + ToIndex(x,y) * NUMTYPES) = val;}/////////////////////////////////////////////////////////////SqBoard::SqBoard( u32 h, u32 w): height(h), width(w),marks(height * width * NUMTYPES, false),safety(height * width * NUMTYPES, true){}bool SqBoard::UpdateWithMove( u32 x, u32 y, u32 type){    assert( IsValid(x,y)    );    assert(type<NUMTYPES);    //place the new mark on the square    SetMark(x,y,type, true);    if ( IsSafe(x,y,type) == false)    {        return true;    }    else    {        //if the square was safe, the game is not over        //check to see if this move causes any other squares to become dangerous        //loop through all posible values for the coordinates of the first side        //std::cout << -((int) x) << " , " << width-x << "\n";        for (int i = -(int) x; i < (int) width- (int) x; ++i)        {            for (int j = -(int) y; j < (int) height- (int) y; ++j)            {                if (i==0 and j==0)                {                    continue; //make sure square has nonzero side length                }                //std::cout << i << "," << j << "\n";                //Make sure that all four corners of the square being tested are inside the playable area                //first corner                if (!IsValid( x + i, y + j ) )                {                    assert( false );                    continue; //This should never happen                }                //second corner                if (!IsValid( x + i + j, y + j - i) )                {   continue;   }                //third corner                if (!IsValid( x + j, y - i ) )                {   continue;   }                //Time to see if 3 of the corners have the same type of mark on them, in which case                //the fourth corner is dangerous for that type of mark                u32 cornercount = 0;                if (IsMarked(x,y,type))                {   ++cornercount;  }                if (IsMarked(x + i, y + j,type))                {   ++cornercount;  }                if (IsMarked(x + i + j, y + j - i,type))                {   ++cornercount;  }                if (IsMarked(x + j, y - i,type))                {   ++cornercount;  }                assert( cornercount < 4);                assert( cornercount > 0); //Because we just marked the first square!                if (cornercount == 3)                {                    //if it is, mark them as unsafe                    SetSafety(x,y,type,false);                    SetSafety(x + i, y + j,type,false);                    SetSafety(x + i + j, y + j - i,type,false);                    SetSafety(x + j, y - i,type,false);                }            }        }    }    for( u32 t=0;t<NUMTYPES;++t)    {        SetSafety(x,y,t,false);        //Obviously we can't move to a square that already has a mark on it    }    return false;}bool SqBoard::UpdateWithMove( SqMove newmove){    return UpdateWithMove( newmove.x, newmove.y, newmove.type );}////////////////////////////////////////////////////////////u32 SqBoard::MakeRandomMoves( u32 count){    std::vector<SqMove> MoveList;    u32 movesmade = 0;    do    {        assert(MoveList.size() == 0);        //Find all possible safe moves        for (u32 x=0;x<width;++x)        {            for (u32 y=0;y<height;++y)            {                for (u32 type=0;type<NUMTYPES;++type)                {                    if (IsSafe(x,y,type))                    {                        MoveList.push_back( SqMove(x,y,type) );                    }                }            }        }        if (MoveList.size() == 0)        {            //std::cout << "We broke!\n";            break;        }        u32 RandIndex = GetRandom() % MoveList.size();        assert(!UpdateWithMove( MoveList.at(RandIndex)));        //EvalAndAssert(!UpdateWithMove( MoveList.at(RandIndex)));        ++movesmade;        MoveList.clear();    }    while (movesmade < count);    return movesmade;}///////////////////////////////////////////////////////////////str SqBoard::ToString() const{    //Converts it to a string for console output. I'll probably remove this once I add a GUI    const str pieces = "XO+!~H=*";    str temp;    temp = str("+");    temp += str( width, '-');    temp += str("+\n");    for (u32 y=0; y<height; ++y)    {        temp += str("|");        for (u32 x=0; x<width; ++x)        {            char nchar = ' ';            for (u32 t=0;t<NUMTYPES;++t)            {                if (IsMarked(x,y,t))                {                    if (t<pieces.size())                    {                        nchar = pieces.at(t);                    }                }            }            temp += nchar;        }        temp += str("|\n");    }    temp += str("+");    temp += str( width, '-');    temp += str("+\n");    return temp;}
I trust exceptions about as far as I can throw them.
Quote:Original post by Storyyeller
Well I read that you are supposed to use asserts to verify things that are supposed to always be true, which is more or less what I've been using them for.


To be a little more accurate: to verify things that are necessarily true according to your understanding of the problem. If an assert ever goes off, and the assert was used properly, it demonstrates that there is a bug in the program.

For things that are "supposed" to be the case (e.g. a file is supposed to be there) but might not be, consider exceptions.
Sorry if I wasn't clear. I meant the first one.

There's no point to using asserts for things that can actually happen, because you'll presumably compile them out eventually and then you're in a bad situation.
I trust exceptions about as far as I can throw them.

This topic is closed to new replies.

Advertisement