Channels ▼
RSS

C/C++

Blundering into the One Definition Rule


A frequently cited rule in the C++ standard is the One Definition Rule (ODR), whereby any unit, template, type, function, or object can have no more than one definition. In this article, I'll show you how I inadvertently blundered into an ODR problem, found my way out, and uncovered a deeper truth about the language standard.

This story starts with a very mundane exercise in debugging using the Windows API function OutputDebugString(), an attempt to simplify my life, followed by a puzzle, and (eventually) enlightenment.

The Pain of OutputDebugString()

If you are a Windows programmer, you are probably familiar with OutputDebugString(). This function allows you to send unadorned strings to the debugger. If you are inside Visual Studio debugging your app, that usually means either your Output or Immediate window. This is useful, but what is even more handy is its use in conjunction with the nifty utility DebugView. DebugView registers with the OS as a collector of these messages and offers some nice filtering while highlighting features — I've been using it for what seems like decades, and still rely on it for help with tough problems. If you are a fan of printf() debugging, this is your tool.

Note that OutputDebugString() is not a general purpose logging tool — in fact, you generally want it removed from production code. It doesn't have the kind of flexibility needed for this purpose, anymore than fprintf() would.

As much as I use it, I have to say that OutputDebugString() really sucks in many respects. First, it only accepts a string as an argument. This means that for anything except the most trivial debugging scenarios, you are going to have to manually format your text using either something from the sprintf() family of C functions or the std::ostringstream class.

It's pretty hard to pull this off without generating an extra three or four lines of code, and then all that stuff needs to be cleaned up or removed when you are done. And the last thing you need when debugging a problem is a lot of extra work.

A Blueprint for Improvement

There are any number of ways to work around this problem. My solution comes about via a number of self-imposed constraints:

  • Using my debugging output facility should require inclusion of a header file — nothing more. No objects to be instantiated, nothing to be added to the class under test.
  • Creating and outputting a formatted debug message of arbitrary complexity should trend to always using just one line of code.
  • Because debugging code that introduces bugs is a bit problematic, the code should be immune to buffer overflows and similar issues.

Depending on your proclivities, your solution is often going to be one of these two:

  • Option One is your own creation along the lines of OutputDebugStringF(), taking a formatting string á la printf(). Programmers who still have C-blood pumping through their veins are going to tend to use this solution, and they can even take advantage of modern language features to eliminate many of the issues that crop up with printf() — no buffer overflows and even no type mismatches.
  • Option Two is a variation on class OutputDebugStream, which can be used to send text to the debugger using iostream formatting. This, of course, gives us immunity from buffer overflows, type safety in formatting, and a standard way to use formatting for user-defined types.

My Implementation

My personal choice is for an iostream-based solution. I am able to completely deploy this anywhere by including the single file below in the C++ code that wants to use it:

#pragma once

#include <sstream>
#include <Windows.h>
#ifndef ODS_PREFIX
#define ODS_PREFIX "ODS: "
#endif

class ods {
  template<typename T> 
  friend ods& operator<<(ods& stream, const T& that);
public :
  ods()
  {
    m_stream << ODS_PREFIX << " ";
  }
  ~ods()
  {
    m_stream << "\n";
    OutputDebugString( m_stream.str().c_str() );
  }
protected :
  std::ostringstream m_stream;
public:
  static ods createTempOds(){ return ods();}
};

template<typename T>
ods& operator<<(ods& stream, const T& that)
{
  stream.m_stream << that;
  return stream;
}

#define ODS ods::createTempOds()

After including this header file in your C++ source, you can send any line you like to the debugger like this:

  ODS << "Value 1: " << val1 << ", Value 2: " << val2;

It's all very easy and the formatting is done using standard C++ rules, so there is no need to learn anything new.

Not shown is the code that you can use to turn this off in production systems: Not only can you prevent it from sending any code to the debugger, but you can easily make sure that the work done to format the data is skipped as well.

Implementation Notes

The core of this code is the ods class. This class contains an std::ostringstream object that accumulates all of the data that is being formatted in the single line for debug output. This member, m_stream, is initialized with a prefix that is defined in a macro, giving me the flexibility to change it in different source files, and making debug output easier to filter and search. When all of the data has been added to this object, it is sent out to the debugger by extracting the C string and passing it to OutputDebugString().

Getting this transaction to work properly depends on the C++ rules regarding temporary objects. The object that is collecting all the data is a temporary created by the ODS macro, which calls ods::createTempOds(). The temporary returned by this function is then the target of the insertion operator <<. Each of the following insertion operators add their data to the temporary object. Section 12.2 of the 1998 spec says this about the lifetime of temporaries:

"Temporary objects are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created."

This is nice because it means that we can count on the ods destructor being invoked at the end of the expression — so OutputDebugString() will be called when we want it to be. If we weren't using a temporary, we couldn't reliably use the destructor to trigger output — it would be called when the object goes out of scope, which may occur later than we need.

One additional complication is that we are using the insertion operator on a user-defined type, ods, that doesn't have this by default. That problem is managed at the end of the header file with a very simple template function definition. Basically, it defines a generic insertion function that inserts the object you want printed into the m_stream object (which actually knows how to deal with it), returning a reference to itself so that the canonical chaining can occur.

This Is So Simple, Nothing Could Go Wrong

As a simple test of this, I created an app with two source files, main.cpp and sub.cpp:

#include "ods.h"

void sub();

int main(int argc, char* argv[])
{
    int foo = 15;
    ODS << "foo: " << 15;
    sub();
    return 0;
}

#define ODS_PREFIX "sub.cpp: "
#include "ods.h"
#include <ctime>

void sub()
{
    ODS << "current time: " << time(NULL);
    return;
}

Note that in the file sub.cpp I used the preprocessor to define a different prefix string. This will allow me to easily flag lines that originate in this file. In main.cpp, the default prefix of ODS: will be used.

When I run this program, the debug window gives me the following output:

ODS:  foo: 15
ODS:  current time: 1393808992

Whoops, something is wrong. The formatting is working, but the same prefix is used in both files. I expected the line with the current time to be prefixed with sub.cpp:. How did this fail?


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.
 

Video