Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Object Registration and Validation


Sanity Checking

Basically, the Object Registration functions extend the member access functions previously described by inserting sanity checking code into each of these member access functions, as well as the object's destructor. Code that sets and clears the object's sanity is also inserted into the object's constructors and destructor, respectively, thereby covering all access points to the object, as shown in Listing 4.

// NON-TRIVIAL CONSTRUCTOR
HPISocket_c::HPISocket_c(string const& User,string const& Password) throw(EXSanity_c)
{
 // CODE OMMITTED FOR BREVITY
 SetSanity(); // THIS OBJECT IS NOW AVAILABLE FOR USE
}

// MUTATOR
String HPISocket_c::DBUser(String const& x) throw(EXSanity_c)
{
 SanityCheck(__LINE__,__FILE__); // VALIDATE OBJECT INTEGRITY
 if(x.length())
	m_DBUser = x;
 return m_DBUser;
}

// ACCESSOR
String HPISocket_c::DBUser() const throw(EXSanity_c)
{
 SanityCheck(__LINE__,__FILE__); // VALIDATE OBJECT INTEGRITY
 return m_DBUser;
}

//DESTRUCTOR
HPISocket_c::~HPISocket_c() throw(EXSanity_c)
{
 SanityCheck(__LINE__,_FILE__); // VALIDATE OBJECT INTEGRITY
 
// CODE OMMITTED FOR BREVITY

 ClearSanity(); // THIS OBJECT IS NOW UNUSABLE
}
Listing 4: Using the ORV functions in constructors, destructors and member access.

This sanity-checking code is responsible for ensuring object integrity and is designed to deal with situations where a consuming object attempts to use an object that is either improperly constructed, incorrectly defined, or has deemed itself unusable for whatever reason. The mechanics of these functions entail very low overhead in that they contain very little code and are fully inlined. Once implemented, there is no way to use an invalid object, as the object will throw a well-formed exception whenever a consuming object tries to invoke its services or use its data.

Implementation of the pattern, detailed in Listings 2 and 3, is pretty straightforward. Let's start with the class declaration. First and foremost, the functions and data required for this pattern are declared private, as previously discussed. It should be the sole discretion of the target object to decide A) when sanity is gained or lost and B) when to check its sanity. Thus, I strongly suggest not making them public. When inheritance is an issue, simply declare these functions protected so child classes need not reinvent the wheel.

OK, now let's visit the actual declarations. First, a static member variable of type long is defined. This variable is used to contain a reference value that all subsequent instances of the class will use. By this I mean that this value, once assigned, will be used to check all instances of this class to determine the sanity of the instance. I usually name this variable sm_clSerialNumber. The sole purpose of this variable is to serve as the yardstick against which all object instances are compared. The value of this member is assigned either inline, or in file scope, depending on the compiler being used. The value assigned is not important, as long as the pattern has a very low probability of being found in random memory. I usually use a large hex number, like 0x001001001.

Next, a mutable member variable is defined, also of type long, which I usually name m_mlSerialNumber. This member variable is used to contain the current registration value. Next, three member functions are declared, each of which is a constant function returning nothing. These functions must be declared constant in order for them to be callable from const member functions of the class. This is also why the local member variable was declared mutable. The three functions are implemented in listing 4, and work as follows:

SetSanity is used to set the local registration variable m_mlSerialNumber to the value contained in sm_clSerialNumber. Once this has been accomplished, the object is deemed to be valid. This function is usually invoked in the constructor, once the function has deemed itself instantiable. Note that returning from the constructor prior to calling this function will leave the object unreachable!

ClearSanity is used to reset the local m_mlSerialNumber to a value other than that found in sm_clSerialNumber. Usually, this value is zero, which I find works best. However, this variable can be set to any arbitrary value that differs from sm_clSerialNumber as needs dictate. This function is usually invoked as the last executable line of the class' destructor. Once this function is invoked, the object is deemed invalid. Subsequent use of the object will throw a well-formed exception.

SanityCheck is used to determine whether the object is valid or not. This function will first check to see if the this pointer of the instance is NULL. If so, the object is being invoked through a null pointer dereference, which, if not dealt with, will cause and access violation (or Segment Fault in Unix variants) that is usually not recoverable. There are mechanisms to deal with these OS faults that are beyond the scope of this article. Next, this function will check to see of the local registration variable is the same as the class wide registration variable. If either condition fails, the function will throw a well-formed exception.

The foregoing is the heart of the pattern and thus deserves a bit more explanation. In C++, the code segment and the data segment are separate. When the code segment is joined with a data segment, we have an object instance. This this pointer in the object points to the data segment the code segment refers to, whether implicitly or explicitly. When this data segment pointer is NULL, or contains an invalid data segment address, the this pointer is invalid and any operation or dereference of it will cause a segment fault. This is exactly what we're looking to be proactive with in the SanityCheck function. If the this pointer is not null, we next check the serial number of the object against the static serial number that is maintained in the code segment, thus immutable. If the values don't match, either we have a garbage pointer or the object was "turned off" by ClearSanity. In either case, the object is unusable and SanityCheck will throw a well-formed exception. A full explanation of how this pointers are bound can be found in the ANSI C++ Standard.

The SanityCheck function is called by every other function in the object, as shown in Listing 5. This will also include the member access functions described earlier as well as the destructor, but exclude the constructors for obvious reasons. By including calls to this function at the top of each member function of the object SanityCheck becomes, in essence, a gatekeeper for the class. Every time a consuming objects attempt to use an invalid object the result is a well-formed exception.

inline DWORD const HPISocket_c::TimeLastUsed() const throw(EXSanity_c)
{
 SanityCheck(__LINE__,__FILE__);
 return m_tTimeLastUsed;
}

inline char const HPISocket_c::DataClass(char x) throw(EXSanity_c)
{
 SanityCheck(__LINE__,__FILE__);
 return m_cDataClass = x;
}
Listing 5: Member Access Functions that use Sanity Checking.

This pattern, then, effectively deals with object-level memory allocation issues such as null pointers, uninitialized pointers, and pointers or references to objects that have been destroyed.

When calling code tries to dereference a class, a well formed exception is thrown. If this exception is not caught, the exception, its message and stack trace will be available in the dump log or, if my exception class hierarchy is used, in the system log (for Solaris) or the Event log (for Windows). Or, with a proper try/catch pair, the exception can be caught, and the application can attempt to recover. Either way, it becomes almost trivial to find and correct these bugs, now that we have a way to find where and when they occurred.

In summary, then, the OVR pattern and the functions that implement them effectively bulletproof classes from unintentional misuse due to simple coding bugs, and provide a simple means to track them down. But in order to truly bulletproof a class, the issue of logic bugs and business rule violations must also be dealt with. That is the subject of a different article.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.