Feed Icon RSS 1.0 XML Feed available

Assertions Parameterized by Location

Date: 1-May-2005/21:09

Tags:

Characters: (none)

Let's say you are implementing a class to represent a Phone. You might want to have the precondition that you cannot hangUp() a phone if it was already hung up.
The typical way to do this is with assert() statements, so in the file phone.cpp you might write:
class Phone
{
private:
   enum Status {
      Dialing,
      Connected,
      Disconnected
   };
   Status _status;

public:
   void hangUp()
   {
      assert(_status != Disconnected);
      /*... some code here...*/
   }

   /*... more functions here ...*/
};
Then let's say in caller.cpp you are modeling someone who wants to have a "typical" phone conversation:
void Alice::callBob()
{
   _phone.dial("1-617-542-5942");
   friendlyGreetings();
   tellBruceSchneierJoke();
   haveSpecialDiscussion();
   _phone.HangUp();
}

void Alice::haveSpecialDiscussion()
{
   std::string password = askForThePassword();
   if (password == "hostilefork") {
      describeWorldTakeover();
      laughManiacally();
   } else {
      talkAboutTheWeather();
   }
   _phone.HangUp();
}
When callBob() executes and hits line 48, the Phone's assertion will fire because it was already hung up during haveSpecialDiscussion() (on line 60). Most implementations of assert will capture the filename and line number where the actual assertion occurs, so you'd probably get a message like:
assertion failure on line 90 of phone.cpp
There's no flexibility to let you indicate another "place" in the source. For instance, what if you wanted the assert to identify the precise offending call to hangUp()?
To address these kinds of needs I created codeplace. One of its many uses is the location-parameterized assert...which lets you pass in a "place" to be identified when the assert triggers. So in phone.cpp you would write:
public:
   void hangUp(const codeplace& cp)
   {
      assert(_status != Disconnected, cp);
      /*... some code here ...*/
   }
Then at the call sites, you would use a special macro called HERE to create a codeplace object which you pass as a parameter. For instance, the assertion-triggering call to hangUp() in caller.cpp would look like this:
_phone.hangUp(HERE(false));
Note the forthcoming documentation for codeplace will explain what false, true, and string parameters mean for "HERE" mean...
Now when the assertion is reported by the program, it will identify the call site. With this change to our example above, you would get something more like:
assertion failure on line 48 of caller.cpp
The location-parameterized assert is only one application of a codeplace. They are helpful abstractions for any time we need to speak about a place in our source. For instance, if we wanted to track the previous disconnection call we could have a local variable in the Phone class to save it... and the tracked<T> template even does this for you automatically. There are some other nuances of the codeplace implementation that are beyond the scope of this particular issue.
You might theorize that run-time access to the stack is the ultimate API for dealing with this sort of thing. Imagine if Phone::hangUp() could somehow obtain an object representing the call stack, and then extract whatever information it wanted about the callers. That's a little heavy-handed, and I believe that the odds of the API being abused are so high that a "narrower" protocol agreement between callers and subroutines would need to be established for the common scenarios.
Business Card from SXSW
Copyright (c) 2007-2018 hostilefork.com

Project names and graphic designs are All Rights Reserved, unless otherwise noted. Software codebases are governed by licenses included in their distributions. Posts on blog.hostilefork.com are licensed under the Creative Commons BY-NC-SA 4.0 license, and may be excerpted or adapted under the terms of that license for noncommercial purposes.