Channels ▼
RSS

C/C++

Blundering into the One Definition Rule


The ODR Steps In

It didn't take me long to realize the mistake I had made: I had violated the One Definition Rule.

In the 1998 C++ standard, the ODR is covered in section 3.2. It is a little too lengthy to transcribe completely here, but I think I can give you the gist of it with two brief excerpts. The first part (which I think of as the Lesser ODR) says that you can't have two definitions of the same thing in a file you are compiling (formally termed a "translation unit"): "No translation unit shall have more than one definition of any variable, function, class type, enumeration type, or template."

Makes sense, right? You don't expect this code to work:

int foo()
{
  return 2;
}
int foo()
{
  return 3;
}

The second part, which I think of as the Greater ODR, says that you can't have two different definitions of a function or object anywhere in your entire program: "Every program shall contain exactly one definition of every non-inline function or or object that is used in that program; no diagnostic is required."

Normally, including a class or function definition in a header file doesn't cause a problem with this rule — every place you use the function, it will have the same definition. But I slipped up in one critical place. This line of code in the constructor uses a macro as part of its definition:

    m_stream << ODS_PREFIX << " ";

This means that the definition of the constructor used in main.cpp is doing this:

    m_stream << "ODS:" << " ";

while the definition in sub.cpp is doing this:

    m_stream << "sub.cpp:" << " ";

Two different defintions, and a sure violation of the ODR!

How It Plays Out

C++ programmers are accustomed to getting a lot of help from the compiler when it comes to obvious mistakes. Things like type safety are inextricably bound up in a reliance on being notified of errors by the compiler when things are done improperly.

In a recent exchange with Herb Sutter over a problem I was having with Visual C++, this very notion was exposed as somewhat weak. I was expecting Visual C++ to reject some invalid C++ code, but for the particular case I was seeing, Herb rightly pointed out:

"Because it is invalid, compilers can do whatever they want with it."

Yes, that's right, there are many, many places where the compiler is not particularly obligated to call out your mistakes. Often it will anyway, but when faced with some types of errors in your program, standard-compliant compilers can pretty much do whatever they like.

You might think this sounds like laziness on the part of the compiler writer, but in the case of the Greater ODR, I think the standard just formalizes the limits of what our current generation of compilers and linkers can handle.

When a function is compiled to object code in two different files, the linker has to select one, and only one, to use in your executable. For a linker to be able to flag Greater ODR violations, it would need to look at the object code generated for every version of a function and guarantee that it is an identical definition. This would be a tremendous amount of work, and it isn't something that linkers do today. So, instead, the linker just picks one and goes with it.

Resolution

Once I realized what was going on, the fix was easy enough. Instead of using the ODS_PREFIX in the body of the constructor, I passed it in to the constructor as an argument:

#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(const char *prefix)
    {
        m_stream << prefix << " ";
    }
    ~ods()
    {
        m_stream << "\n";
        OutputDebugString( m_stream.str().c_str() );
    }
protected :
    std::ostringstream m_stream;
public:
    static ods createTempOds(const char *prefix){ return ods(prefix);}
};

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

#define ODS ods::createTempOds(ODS_PREFIX)

Now, all copies of the function are identical, and my output is correct:

ODS:  foo: 15
sub.cpp:  current time: 1393914308

So, going forward, remember to make sure your p's are p's and your q's are q's, and certainly don't assume that a clean compile means your code is legal.


Mark Nelson is the author of C++ Programmer's Guide to the Standard Template Library and a longtime contributor to Dr. Dobb's.


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