Search code examples
c++c++11linkershared-ptrboost-optional

Different versions of destructor called depending on strange factors


Why do the following code generate such output?

main.cpp ctor 0x24a4c30
test.cpp dtor 0x24a4c30

test.cpp

#include <boost/optional.hpp>

struct Test
{
    Test()
    {
        printf("test.cpp ctor %p\n", (void *) this);
    }

    ~Test()
    {
        printf("test.cpp dtor %p\n", (void *) this);
    }
};

boost::optional< Test > t;

main.cpp

#include <memory>

struct Test
{
    Test()
    {
        printf("main.cpp ctor %p\n", (void *) this);
    }

    ~Test()
    {
        printf("main.cpp dtor %p\n", (void *) this);
    }
};

int
main(void)
{
    std::make_shared< Test >();
    return 0;
}

Compiled with

g++ -std=c++11 -c test.cpp -o test.o
g++ -std=c++11 -c main.cpp -o main.o
g++ -std=c++11 test.o main.o

I explain this behaviour that the test.o provide Test's ctor & dtor then a linker discard duplicated symbols from main.o, but it's correct only for dtor. If I delete the static object t then a linker discard symbols from test.o and the output is next

main.cpp ctor 0x208ec30
main.cpp dtor 0x208ec30

Solution

  • As stated in the comments, defining the same symbols in two linked translation units violates the One Definition Rule, and while

    There can be more than one definition in a program, as long as each definition appears in a different translation unit, of each of the following: class type, enumeration type, inline function with external linkage, inline variable with external linkage (since C++17), class template, non-static function template, static data member of a class template, member function of a class template, partial template specialization, concept (since C++20) as long as all of the following is true:

    • each definition consists of the same sequence of tokens (typically, appears in the same header file)
    • [...]

    If all these requirements are satisfied, the program behaves as if there is only one definition in the entire program. Otherwise, the behavior is undefined. (emphasis mine)

    Observed results of UB don't need explanation, and may not have any.

    Sure, this is a blatant violation. However, a very similar situation can appear (and cause strange and hard to locate bugs) in more innocent-looking programs. For example, linking against an older separately compiled object when some class declarations changed.