C++ Automatically Implemented Functions and the ODR

214 views Asked by At

Long explanation follows, question at bottom.

My question specifically refers to the current C++ draft standard (but also the current 'main' standard) found here. More specifically, section 3.2 point 6 (page 35) states each definition of D shall consist of the same sequence of tokens, with regard to member functions and the ODR.

I recently encountered the following problem in a project while I was adding a new data analysis.

I was writing a file, A.cpp. I created a small dummy struct to hold some Data. In this example, I will call it Data.

namespace Example {
struct Data {
    //etc
};
//Use Data
};

However in anther file, B.cpp, there was already a struct called Data inside the Example namespace. The compiler generates Data::~Data(); for both classes, which in turn calls the destructors of their respective members. The definition in B.cpp contains a vector, which when destructed caused explosions when called on Data structs using the layout defined in A.cpp. While both structs appear to work correctly, with no compile time errors, it appears that at link time the linker would pick one definition and use that, ignoring the other definition. (Hence caused explosions on Data objects inside of A.cpp)

No warning is issued under GCC or under MSVC. When optimisation is enabled the problem does not occur (the functions are inlined, no link time confusion).

My question is, the standard only states that the behaviour is undefined If D is a template and is defined in more than one translation unit. Either I have misunderstood the standard, and the undefined behaviour is allowed to silently occur; or both GCC and MSVC are silently accepting something they shouldn't (and should either refuse to produce an output or issue a warning) (The current situation is undefined and inconsistent behaviour without a diagnostic).

Could someone please help me understand how this is different to conflicting definitions of functions that are not defined in classes (which do cause warnings/errors).

2

There are 2 answers

4
Tony Delroy On BEST ANSWER

Could someone please help me understand how this is different to conflicting definitions of functions that are not defined in classes (which do cause warnings/errors).

The difference is that function definitions inside a class are implicitly nominally inline, which inhibits the compiler warnings if the function's encountered again. That doesn't mean the compiler has to inline them - it may decide using whatever heuristics not to bother, or it may simple never inline at some optimisation levels. Anyway, if you link code that's seen different definitions of nominally inline non-member functions you have exactly the same problem.

See 3.2/6

There can be more than one definition of a class type ... in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements.

— each definition of D shall consist of the same sequence of tokens; and

[others requirements]

More generally, you should have put your code into anonymous namespaces... they're designed to prevent cross-translation-unit problems of this kind.

1
John Zwinck On

It doesn't matter what is being multiply-defined, it's undefined behavior to do it. And undefined behavior can result in anything--including a working program with no diagnostics from the compiler or runtime. Defining the same name in two different ways is not well-formed code, regardless of whether it is a member function, a free function, a struct or class, a global variable, etc.