21

I have made two identical classes X and Y, with a pointer to each other. See the code below for X.h, Y.h is identical with all X's and Y's interchanged. This code gives however an error in my method Connect (error C2027: use of undefined type 'Y'). In X.h, I have forward declared the class Y, but it doesn't know that Y has a method named SetXPointer. Therefore I also need to forward declare this method, correct?

If I try to do this (adding the line Y::SetXPointer(X* pX_in); under the line class Y;), I get a compiler error C2761: 'void Y::SetXPointer(X *)' : member function redeclaration not allowed. Is there a way to use a public method of class Y in class X?

// X.h

#pragma once

#include "Y.h"

// Forward declaration
class Y;

class X
{
public:
    X(void) : data(24) {};
    ~X(void) {};
    int GetData() { return data; }
    void SetYPointer(Y* pY_in) { pY = pY_in; }
    Y* GetYPointer() { return pY; }
    void Connect(Y* Y_in) { pY = Y_in; Y_in->SetXPointer(this); }
private:
    int data;
    Y *pY;
};
physicalattraction
  • 6,485
  • 10
  • 63
  • 122

4 Answers4

25

Don't include the method body in the class body. Write both classes, and after both classes are complete, write the method implementations:

class Y;
class X {
  …
  void Connect(Y* Y_in);
  …
};
class Y {
  …
  void Connect(X* X_in);
  …
};
inline void X::Connect(Y* Y_in) {
  pY = Y_in;
  Y_in->SetXPointer(this);
}
inline void Y::Connect(X* X_in) {
  pX = X_in;
  X_in->SetXPointer(this);
}

That way, full information about how the objects of the class will be layed out in memory is available by the time the Connect method is implemented. And as a method in the class body and a method declared inline will both be inlined the same way, performance will be the same as well.

The only downside is that you won't be able to split these two classes over two headers in a reasonable way.

MvG
  • 57,380
  • 22
  • 148
  • 276
  • I combined both answers and put the implementation as above in separate cpp files. If I remove the keyword inline, it works, otherwise, I get a linker error LNK2019: unresolved external symbol "public: void __thiscall Y::Connect(class X *)" (?Connect@Y@@QAEXPAVX@@@Z) referenced in function _main – physicalattraction Jul 05 '12 at 12:41
  • 2
    `inline` functions must go into the headers where their classes are. Otherwise, the compilation unit where they are implemented won't generate linker symbols for them, as it expects them to be inlined by the compiler. Every other compilation unit will not know about the inlining, and thus generate references to symbols which are never available. – MvG Jul 05 '12 at 12:55
4

The only way to do this, if both your classes need a full type in their methods, is to separate the implementation to an implementation file.

Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
  • 1
    It has to be outside the class body, but not necessarily in a separate file. And if you want to keep it inlined, it has to be a header file, not an implementation file. – MvG Jul 05 '12 at 12:33
  • @MvG suggesting a common header, although it would work, is not good advice. – Luchian Grigore Jul 05 '12 at 12:44
  • Why? The classes are closely related. If you want to inline stuff, neither can be included without the other. So why shouldn't they share a single header file? – MvG Jul 05 '12 at 12:53
  • @MvG why do you need the methods to be inline? – Luchian Grigore Jul 05 '12 at 12:54
  • Not neccessarily *need* to. Main argument would be performance, and another would be staying as close to the original question as possible in terms of the generated machine code. I know I would inline something like this, as two simple assignments aren't worth the effort of a method call in my book. – MvG Jul 05 '12 at 13:00
  • @MvG I doubt the bottleneck is a function call. And it's all about code organization. You're not the only one using it, remember. Do you know where I can find class X's definition? Sure, it's in Y.h... – Luchian Grigore Jul 05 '12 at 13:01
3

You can place class B into class A

using namespace std;

class A
{
        public:
        void run()
        {
                B b("hi");
                b.run(this);
        }

        void print(string &msg) { cout << msg << endl; }

        private:
        class B
        {
                public:
                B(string m) : msg(m) {}
                void run(A *a) { a->print(msg); }

                private:
                string msg;
        };
};

int main()
{
        A a;
        a.run();
        return 0;
}
JayS
  • 2,057
  • 24
  • 16
2

If you intend to share large parts of the implementation between X and Y, you might want to do that using a template. One example is the following:

template<bool isX> class XY
{
public:
  typedef XY<!isX> YX; // This is the opposite type to the current one.
  XY(void) : data(24) {};
  ~XY(void) {};
  int GetData() { return data; }
  void SetPointer(YX* pYX_in) { pYX = pYX_in; }
  YX* GetPointer() { return pYX; }
  void Connect(YX* YX_in) { pYX = YX_in; YX_in->SetPointer(this); }
private:
  int data;
  YX *pYX;
};

typedef XY<true> X;
typedef XY<false> Y;

As template methods are only instantiated when used, you avoid the problem outlined above, as by the time they are instantiated, both types are known. If you later on have differences between X and Y, you might use inheritance instead of typedef.

MvG
  • 57,380
  • 22
  • 148
  • 276