Home > Software engineering >  Pointer losing visibility during cross-class access
Pointer losing visibility during cross-class access

Time:08-04

I have an issue where class GUI_System accesses a member of class ThreadManger through class GUI_Top. What happens is that accessing the member from its original container shows its correct contents, but when accessed from GUI_System it doesn't.

I have found out that in class ThreadManger, prior to the member in question, there are some other objects that if i comment them out, the behavior is changed.

  1. if i leave everything in, then StructLoadSystem has an address of 0x0 enter image description here
  2. if i comment out one of the two method pointers then the address of StructLoadSystem is not 0x0 anymore, but is still different from that seen from ThreadManger. enter image description here
  3. if i further comment out some more objects, then the address returns to be 0x0. enter image description here

My issue happens on a big project of mine with a lot of interdependent files so i have stripped to the bare minimum the code in order to create a test-case. Please also note that i have to keep the files separate, because for some reason if i put everything in the same file, the issue disappears so including order might be a thing to look into.

For convenience i have created a Visual Studio solution that can be launched directly. You can find it here.

Can someone give any ideas of what could be happening and/or what i could do to further debug this?

CodePudding user response:

I downloaded your project and had a look at it. The primary issue seems to be that you are storing a pointer to a method on a forward-declared type. The size and/or padding for such values is unknown without the definition of what it points to.

Here is your ThreadManger [sic] stripped back:

class GUI_System;

class ThreadManger
{
    class JobsClass
    {
    public:
        void (GUI_System::* EndEvent)();
    };

public:
    JobsClass MyJobs;                      //<-- Unknown size, if GUI_System is not defined
    ServiceStruct* StructQuitApplication;  //<-- Offset might change depending on above
};

And so, in ThreadManger.cpp you have this situation:

//<-- At this point, GUI_System is NOT defined
#include "ThreadManger.h"

Whereas in GUI_System.cpp you have this:

#include "GUI_System.h"
//<-- At this point, GUI_System IS defined
#include "GUI_Top.h"     //<-- This includes ThreadManger.h

When I compile the project as 32-bit, the following occurs:

  • ThreadManger thinks that sizeof(ThreadManger::MyJobs) is 16
  • GUI_System thinks that sizeof(ThreadManger::MyJobs) is 4

When I compile the project as 64-bit, the following occurs:

  • ThreadManger thinks that sizeof(ThreadManger::MyJobs) is 24
  • GUI_System thinks that sizeof(ThreadManger::MyJobs) is 8

That means the offset of ThreadManger::StructQuitApplication is different in these two translation units and in fact the issue is caused by your ThreadManger implementation, not GUI_System. It's a nasty one for sure, being sensitive to the order of header inclusion.

The quick fix is to #include "GUI_System.h" in ThreadManger.h, instead of forward-declaring it.

That's not to say this is a "good" fix, because more fundamentally your design is much too interconnected. The tight coupling of these classes all knowing about each-other's members is something that should be avoided. And nothing quite illustrates that better than the issue you've just encountered. I highly recommend you rethink your design.

At the very least, you ought to be making a lot of this stuff private and expose functionality via methods. As for the JobsClass stuff, you can use the PIMPL idiom by forward-declaring it and storing privately as a pointer. Then, in the implementation you would ensure you have included GUI_System.h so that the class can be properly defined.

You may also consider using std::function and lambdas instead of pointer-to-method for your jobs. Alternatively, define an abstract Job class with a Execute method, which is called when dispatching an event. Whichever way you do it, ideally a "thread manager" should be general purpose and not need to know anything about who or what is using it.

  • Related