Search code examples
c++linkageone-definition-rule

Are classes allowed to have different definitions across different translation units in a program?


Is it well-formed to define a class differently in different translation units granted that the class is defined at most once in each translation unit?

Use case is accessing implementation details without dynamic allocation. C++ code will operate on pointers already allocated by a C library.

Please ignore memory leaks for the sake of example.

common.hpp

#pragma once

namespace Test {

class Impl;

class A {
    void *ptr;
    A(void *ptr) : ptr(ptr) {}
    friend class Impl;

   public:
    int plus_one();
};

class B {
    void *ptr;
    B(void *ptr) : ptr(ptr) {}
    friend class Impl;

   public:
    int plus_two();
};

class Factory {
   public:
    A getA(int val);
    B getB(int val);
};

}  // namespace Test

A.cpp

#include "common.hpp"

namespace Test {

class Impl {
   public:
    static int as_int(A *a) { return *static_cast<int *>(a->ptr) + 1; }
};

int A::plus_one() { return Impl{}.as_int(this); }

}  // namespace Test

B.cpp

#include "common.hpp"

namespace Test {

class Impl {
   public:
    static int as_int(B *b) { return *static_cast<int *>(b->ptr) + 2; }
};

int B::plus_two() { return Impl{}.as_int(this); }

}  // namespace Test

Factory.cpp

#include "common.hpp"

namespace Test {

class Impl {
   public:
    static A getA(int val) { return A(new int{val}); }
    static B getB(int val) { return B(new int{val}); }
};

A Factory::getA(int val) { return Impl{}.getA(val); }
B Factory::getB(int val) { return Impl{}.getB(val); }

}  // namespace Test

main.cpp

#include <iostream>

#include "common.hpp"

int main() {
    Test::Factory factory;
    std::cout << factory.getA(1).plus_one() << std::endl;
    std::cout << factory.getB(1).plus_two() << std::endl;
    return 0;
}

Output:

$ g++ A.cpp B.cpp Factory.cpp main.cpp -o test 
$ ./test
2
3

Solution

  • No, the same class type is not allowed to have different definitions. Your program is in a direct violation of the ODR, and exhibits undefined behavior.

    [basic.def.odr]

    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. Given such an entity named D defined in more than one translation unit, then

    • each definition of D shall consist of the same sequence of tokens; and
    • [...]

    [...] If the definitions of D satisfy all these requirements, then the behavior is as if there were a single definition of D. If the definitions of D do not satisfy these requirements, then the behavior is undefined.

    Your two definitions quite obviously differ in their token sequences already, and so the conditions of the ODR are not upheld.