0

I have a base class with a pure virtual method implemented by two classes:

// base_class.hpp
class base_class {
public:
  virtual std::string hello() = 0;
};

// base_implementer_1.hpp
class base_implementer1 : base_class {
public:
  std::string hello();
};

// base_implementer_2.hpp
class base_implementer2 : base_class {
public:
  std::string hello();
};

// base_implementer_1.cpp
std::string hello() {
  return(std::string("Hello!"));
}

// base_implementer_2.cpp
std::string hello() {
  return(std::string("Hola!"));
}

Note the lack of base_implementer1:: and base_implementer2:: in the implementations. This is deliberate.

By adding in the base_implementer1:: and base_implementer2:: I do not get a multiple definition error. However, leaving them off the linker complains I have two definitions of the same function (hello()).

Since these two implementations are not featured in the header files, I would think that (even though they are not correct in terms of ACTUALLY implementing hello()) they would be allowed since there's no reason you couldn't have two hello() functions in two distinct .cpp files. But this doesn't seem to be the case. Can anyone tell me what's happening in the linker to make this multiple definition error happen?

  • "since there's no reason you couldn't have two hello() functions in two distinct .cpp files" - yes, there is. The linker sees two functions with the same signature - that's a multiple definition. –  Jan 09 '17 at 00:42
  • You can have two `hello()` in two distinct .cpp files, but you can't link them together. Otherwise, when `hello()` is called (e.g. in `main.cpp`) which definition should be used? – songyuanyao Jan 09 '17 at 00:44
  • Just because your `hello` implementation is in base_implementer_1.cpp does not mean that it is part of the `base_implementer_1` class. It only becomes part of the class when you pretend `hello` with `base_implementer_1::`. The same goes for `base_implementer_2`. In other words, your deliberate lack of prepending the method implementation with the class name is your problem. – evan Jan 09 '17 at 01:12
  • Please post a MCVE. The current code would fail to compile as `std` is not defined. – M.M Jan 09 '17 at 01:31
  • @M.M this is a toy example to illustrate a concept. It's ability to be compiled does not enhance the understanding of the question at hand. We can imagine these files being declared inside of a place that includes the necessary `#includes` (, etc) –  Jan 09 '17 at 03:04
  • @rec it's significant whether or not `hello` is *odr-used* (e.g. whether it is ever called or not) – M.M Jan 09 '17 at 03:13

3 Answers3

2

One-Definition-Rule defines rules for two scopes, i.e. translation unit scope and program scope.

The following rule with translation unit scope states that the same translation unit must not comprise two different definitions of the same function:

Only one definition of any variable, function, class type, enumeration type, or template is allowed in any one translation unit (some of these may have multiple declarations, but only one definition is allowed).

So, if you have two different .cpp-files, than you have two different translation units, and each of them may have their own definition of hello(); ODR is not violated in the scope of a translation unit.

The following rule with program scope defines that an odr-used function must be defined exactly once in the program:

One and only one definition of every non-inline function or variable that is odr-used (see below) is required to appear in the entire program (including any standard and user-defined libraries). The compiler is not required to diagnose this violation, but the behavior of the program that violates it is undefined.

The definition of odr-used informally states that for every function that is called or which's address is taken must be defined in the program:

Informally, an object is odr-used if its address is taken, or a reference is bound to it, and a function is odr-used if a function call to it is made or its address is taken. If an object or a function is odr-used, its definition must exist somewhere in the program; a violation of that is a link-time error.

So, if more than one .cpp-file exposes an implementation of hello(), and if this function is called or referenced, then ODR from program scope is clearly violated.

If the respective function is not odr-used (i.e. called or referenced), ODR should - to my understanding - not be violated;

If a compiler complains about duplicate symbols, than this is because the program violates linkage rules (please confer also SO answer concerning "If I don't odr-use a variable"). C++11 §3.5[basic.link]/9 states:

Two names that are the same and that are declared in different scopes shall denote the same variable, function, type, enumerator, template or namespace if

  • both names have external linkage or else both names have internal linkage and are declared in the same translation unit; and ...

To avoid this, make sure that at most one implementation of hello() is exposed, and make all others static or use an unnamed namespace.

In the C programming language, static is used with global variables and functions to set their scope to the containing file, i.e. it does not expose this implementation and name clashes with other binaries are avoided.

So a reasonable suggestion would be: Make function definitions, that are solely used within a translation unit, visible only to this translation unit; and define functions that are exposed within a namespace or class in order to avoid unintended or unforeseeable name clashes / duplicate symbol problems in the linker.

Community
  • 1
  • 1
Stephan Lechner
  • 34,891
  • 4
  • 35
  • 58
  • This is the most complete answer. Thank you. –  Jan 09 '17 at 01:15
  • 2
    OP does violate ODR. Your quote is not the complete definition of ODR. See the next paragraph after the quoted one in the linked page. – eerorika Jan 09 '17 at 01:19
  • @user2079303 Technically it only violates ODR if `hello` is *odr-used* , which it isn't in OP's code. [See this thread](http://stackoverflow.com/questions/12978745/if-i-dont-odr-use-a-variable-can-i-have-multiple-definitions-of-it-across-tran). It would be good if this answer was a bit more specific about that – M.M Jan 09 '17 at 01:42
  • Can OP post a more complete answer per @M.M? I will leave this the selected answer providing Stephan does that. –  Jan 09 '17 at 03:03
  • @M.M: thanks for your input! AT_rec: reworked the answer; hope argumentation is consistent and complete now. – Stephan Lechner Jan 09 '17 at 09:59
0

You define a global function called hello in base_implementor_1.cpp. You define another global function called hello in base_implementor_2.cpp. This results in the multiple definition and the required error for violation of the ODR. Why is this a problem? If you have a 3rd source file that calls hello(), which function should be called?

If you want to define distinct functions with the same name in multiple source files, you can preface them with the static keyword

static void hello() { }

or within an anonymous namespace

namespace {
    void hello() { }
}
1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56
0

You have two different definitions of a function named hello in two different translation units. When it comes to link time, the linker has no idea which hello function to link to.

Consider:

A.cpp

#include <string>
std::string hello() {
    return "A";
}

B.cpp

#include <string>
std::string hello() {
    return "B";
}

C.cpp

#include <iostream>
std::string hello();
int main() {
    std::cout << hello() << '\n';
}

How could the linker possibly know which hello to call in main? It can't because the One Definition Rule has been violated.

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52