Sign in to follow this  
Storyyeller

Assert with side effects

Recommended Posts

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

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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;
}



Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
Quote:
Original post by Storyyeller
I wanted to assert the value of a function that had side effects.

That's a bad idea. The simple solution is: don't do that. Only assert pure expressions.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this