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 á laprintf()
. 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 withprintf()
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 usingiostream
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?