4

I am working on translating some Java code into C++. When I try to write code like:

.h:

class A {
  private:
    class B;
    std::set<B> b_set;
};

.cpp:

class A::B {
};

I got an incomplete type error. I understand that that is because the nested class is incomplete before using it in b_set. But what's the best way to fix it?

AndyG
  • 39,700
  • 8
  • 109
  • 143
xieziban
  • 77
  • 1
  • 5

2 Answers2

2

You can describe your entire B class in the .h file.

Here's a working example.

#include <set>

class A {
  private:
    class B{
        B() : foo(1) { }
        int foo;
    };
    std::set<B> b_set;
};

However, if you want to separate your definition and instantiation, you can do this:

A.h

#include <set>

class A {
  private:
    class B {
      public:
        B();
        
      private:
        int someMethod();
        int foo;
    };
    std::set<B> b_set;
};

A.cpp

#include "A.h"

A::B::B() : foo(1) { }

int A::B::someMethod() {
  return 42;
}

Generally speaking, nested classes can be a serious PITA because of all the hoops you have to jump through to access anything from them.

Another good reference on nested classes: Nested class definition in source file

coulomb
  • 698
  • 6
  • 16
AndyG
  • 39,700
  • 8
  • 109
  • 143
  • Thanks Andy! But in this case, the implementation is in ".h" file. Is it a good practice? – xieziban Apr 10 '14 at 18:17
  • @xieziban: See my edit. You would need a really good argument for using a nested class, so the blanket statement it to not have one at all. – AndyG Apr 10 '14 at 23:40
2

Well, I'm late, I know, still I want to point out another possibility, if you want to completely hide away the internals of class B:

class A
{
  private:
  class B;
  std::set<B*> b_set;
};

Notice using pointers in the set. However, there is yet left an important difference: as only pointers are inserted, you can still insert pointers to different instances having the same content. To solve this, you need a custom comparator:

class A
{
  private:
  class B;
  struct Less
  {
    bool operator() (B const* x, B const* y) const
    {
      return *x < *y;
    }
  };
  std::set<B*, Less> b_set;
};

Be aware (this was not mentioned in the previous answer, but is required there, too!) that there must be defined a comparator for B (B or reference to, not pointer!):

A.h

#include <set>
class A
{
  private:
  class B;
  struct Less
  {
    bool operator() (B const* x, B const* y) const;
  };
  std::set<B*, Less> b_set;
};

A.cpp

class A::B
{
  friend bool Less::operator() (B const* x, B const* y) const;
  bool operator<(B const& other) const
  {
    return foo < other.foo;
  }
  int foo;
};

bool A::Less::operator() (B const* x, B const* y) const
{
  return *x < *y;
}

This allows to hide away B completely from the header, if you want or need to for any reason. However, you cannot insert objects from the stack directly any more, as they are not copied and you have pointers to the stack that quickly get invalid. Special care has to be taken for deleting the objects when they are not needed any more, or you get memory leaks. Remember that there is no garbage collection as you know from Java. If using C++11, you can alleviate the problem using ::std::unique_ptr, before, ::std::auto_ptr:

A.h

#include <set>
#include <memory>
class A
{
  private:
  class B;
  struct Less
  {
    bool operator() (B const* x, B const* y) const;
  };
  std::set<std::unique_ptr<B>, Less> b_set;
};
Aconcagua
  • 24,880
  • 4
  • 34
  • 59