1

I have a project with a situation where a function of a wrong object is being called.

To simplify it, I created a new CPP project in visual studio. The project has three files (attached at the bottom) with 2 modules, derived_1 and derived_2 which contain class base_t and override a virtual function of an interface class.

When attempting to call the virtual function of one object I see that it is the function of the other object which is being called.

I also opened the map file and saw that the wrong method is the one being linked.

base.hpp:

#ifndef BASE_HPP
#define BASE_HPP

extern int globalFlag;

class base_t {
public:
    struct callBack_t {
        virtual void virtualFunction(void) = 0;
    };
    
    base_t(callBack_t& callBack)
        : _callBack(callBack) {}

    void run() { _callBack.virtualFunction(); }

    callBack_t& _callBack;
};

#endif //BASE_HPP

derived_1.cpp:

#include "base.hpp"

class derivedCallBack_t : public base_t::callBack_t
{
public:
    void virtualFunction(void) {
        globalFlag = 1;
    }
};

class derived_1_t
{
public:
    derived_1_t(void);
    
    derivedCallBack_t* _derivedCallBack;
    base_t _base;
};

derived_1_t::derived_1_t(void):
    _derivedCallBack(new derivedCallBack_t()),
    _base(base_t(*_derivedCallBack))
{}

derived_2.cpp:

#include <iostream>
#include "base.hpp"

class derivedCallBack_t : public base_t::callBack_t
{
public:
    void virtualFunction(void) {
        globalFlag = 2;
    }
};

class derived_2_t
{
public:
    derived_2_t(void):
        _derivedCallBack(new derivedCallBack_t())
        , _base(base_t(*_derivedCallBack))
    {}

    derivedCallBack_t* _derivedCallBack;
    base_t _base;
};


int globalFlag = 0;
int main()
{
    derived_2_t derived = derived_2_t();
    derived._base.run();
    std::cout << "globalFlag " << globalFlag; //expecting globalFlag to be 2
}
Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
Hagai
  • 11
  • 2

2 Answers2

7

Your program is ill-formed, no diagnostic required (IFNDR), because class derivedCallBack_t is defined in two files in two different ways. This violates the One Definition Rule. IFNDR means your program isn't valid C++, and executing it is undefined behavior.

It's generally possible to have class definitions in multiple files; see How can a C++ header file include implementation?. However, the definition has to be the same everywhere.

  • derivedCallBack_t in one file sets globalFlag = 1
  • derivedCallBack_t in another file sets globalFlag = 2

What happens in practice is that the linker can assume that if there are multiple definitions, they all have to be exactly the same. It then arbitrarily picks one of the two definitions, and this happened to be the one which sets globalFlag = 1 in your case.

Solution

EVERYTHING that is declared and used only in one source file needs to have internal linkage (see What is external linkage and internal linkage?). Otherwise, there is no way to ensure safety. Nothing prevents you from having conflicting definitions in multiple files by accident.

// derived_1.cpp
namespace {
class derivedCallBack_t : public base_t::callBack_t { /* ... */ };
class derived_1_t { /* ... */ }; // note: you could just call this derived_t
}
// derived_2.cpp
namespace {
class derivedCallBack_t : public base_t::callBack_t { /* ... */ };
class derived_2_t { /* ... */ };  // note: you could just call this derived_t
}

Putting classes in an unnamed namespace gives them internal linkage, so the two classes will have the same name, but be entirely distinct.

The same principle applies to derived_1_t and derived_2_t. Adding a number to the name doesn't prevent you from making the same mistake in a different place.

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
4

Your program is in violation of One Definition Rule because there are two incompatible definitions of derivedCallBack_t::virtualFunction. The behaviour is undefined.

You have to rename one of the classes named derivedCallBack_t, or put the two classes in two different namespaces. I suppose you want two different classes here, each one local to its respective translation unit. In this situation unnamed namespace do the trick:

namespace {
   class derivedCallBack_t : public base_t::callBack_t // etc
}
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243