2

Lets say I have something like the following:

a.hpp:

class B;

class A
{
private:
  std::unique_ptr<B> b_;
}

a.cpp:

#include <something_complicated.hpp>

struct B
{
  something_complicated x;
}

something_complicated& A::get_complicated() { return b_->x; }

Unfortunately, in this case, a.cpp will fall to compile because "get_complicated()" is not a method of A.

So, we can try this:

a.hpp:

class B;

class A
{
private:
  std::unique_ptr<B> b_;
  something_complicated& A::get_complicated();
}

But then a.hpp fails to compile because something_complicated isn't defined.

We could forward declare something_complicated if it is a class, but it's probably a typedef, so that is out.

The only way I can think of doing this without making b_ public nor including something_complicated.hpp in a.hpp is the following:

a.cpp:

#include <something_complicated.hpp>

struct B
{
  something_complicated x;
}

#define get_complicated ((b_->x))

Surely I don't have to define a macro to get around this issue? Any alternatives?

Clinton
  • 22,361
  • 15
  • 67
  • 163
  • 4
    I don't think you should need this. If A is a pImpl, with B being the Impl, then all the work should be done in functions of B. The functions of A (except of course lifecycle stuff like `swap`) should just call those. So the "alternative" is to move all the code that currently uses `get_complicated` into functions of B, so that `get_complicated` can be replaced with `x`. – Steve Jessop Apr 04 '11 at 12:27
  • 1
    *Note: if you forward declare `B` as a `class` and then define it as a `struct`, it is undefined behavior. The compiler is free to mangle the names differently, so be consistent.* – Matthieu M. Apr 04 '11 at 19:25

6 Answers6

2

The easiest solution is probably to wrap a reference to the complicated type in a class, forward declare that in a.hpp, and define it in something_complicated.hpp.

a.hpp:

class B;
class complicated_ref;

class A
{
public:
  complicated_ref get_complicated();
private:
  std::unique_ptr<B> b_;
};

something_complicated.hpp:

// ... complicated definitions ...
typedef whatever something_complicated;

struct complicated_ref
{
    complicated_ref(something_complicated & thing) : ref(thing) {}
    something_complicated & ref;
};

Now a.cpp and anything that needs to use the complicated type must include it's header, but anything that just wants to use class A does not need to.

This is assuming that there's a good reason for some clients of A to access the complicated thing, but for B to be inaccessible to everyone. It would be simpler still to allow access to B when required, and get to the complicated thing through that.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
1

Just avoid referring to something_complicated in a.hpp.

One solution is to replace the member function get_complicated with a free function, or a static method of another class.

.h:

class A_impl_base {
    A_impl_base() {}

    friend class A_impl; // all instances of A_impl_base are A_impl
}; // this stub class is the only wart the user sees

class A
{
private:
    std::unique_ptr< A_impl_base > b_; // this is not a wart, it's a pimpl

    friend class A_impl;
}

.cpp:

class A_impl : A_impl_base {
     static A_impl &get( A &obj ) { return * obj.b_; }
     static A_impl const &get( A const &obj ) { return * obj.b_; }
};
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • I know it's legal, but I don't see why I should have to do this. There's no reason why the client should need to know about "something_complicated.hpp", it's an implementation detail that is not in the public interface of A. Indeed, I should be able to compile A as a library and the client shouldn't even need to have "something_complicated.hpp" to link against it. – Clinton Apr 04 '11 at 12:12
  • I subscribe to this solution, however feel free to extract the typedef of the something_complicated from the header file into another header file, if you really don't want to share with the a.hpp what the something complicated header file defines ... – Ferenc Deak Apr 04 '11 at 12:14
  • @Clinton: The pimpl idiom involves dividing functionality between public and private classes. Your problem stems from having private details in the public class. If you want something to be private, don't make it public. The problem is that there is no pimpl here. – Potatoswatter Apr 04 '11 at 12:15
  • fritzone: Thanks for the idea, if it's that complicated I'd rather just use the macro. I'm looking for something nicer than the macro, not more difficult. – Clinton Apr 04 '11 at 12:16
  • Potatoswatter: What am I making public that should be private? – Clinton Apr 04 '11 at 12:18
  • @Clinton: I don't mean `public` and `private`, but rather what identifiers are used in the header which the user sees. Separate implementation details into another class, and reserve the public (user-visible) class for the user interface. I'll update my answer in a second. – Potatoswatter Apr 04 '11 at 12:24
  • @Potatoswatter: So I guess this means I have to use get(*this) yes? – Clinton Apr 04 '11 at 12:55
  • @Clinton: Something like `void A::do_something() { A_impl::get( *this ).do_something(); }` – Potatoswatter Apr 04 '11 at 13:01
1

What's wrong with:

a.hpp:

class B;

class A
{
private:
  std::unique_ptr<B> b_;
public:
  B& get_B();
}

If your clients want to get something complicated out of B, then let them #include <something_complicated.hpp>.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
1

I am afraid there is a misunderstand on what belong to the class, and what does not.

Not all methods that act on the internals of the class should be class methods, after all, we have friend functions already. I know that many people declare the helper methods as private functions, however doing so introduces needless dependencies (compile-time) and a visibility issue with friends.

When dealing with PIMPL, I tend not to use private functions. Instead, the choice is:

  • Making Impl (B in your case) a true class, with its own validation logic and true API
  • Using static free functions (or functions declared in an anonymous namespace)

Both are good, and use whichever seems most appropriate. Namely:

  • methods: when dealing with validation issues
  • free functions: for computing that can be expressed in terms of the aforementionned methods

It is deliberate on my part to search to have as few methods as possible, because those are the only ones that can screw up my class invariants, and the less they are the more confident I can be that the invariants will be maintained.

In your case, it's up to you to decide which approach suits you best.

In Action:

a.cpp

#include <something_complicated.hpp>

struct B
{
  something_complicated x;
}

static something_complicated& get_complicated(B& b) { return b_.x; }

// or anonymous namespace instead
namespace {
  something_complicated& get_complicated(B& b) { return b_.x; }
}

Not so different from what you had, eh ?

Note: I prefer static functions to anonymous namespaces because it's more obvious when reading. Namespaces introduce scopes, and scope are not glanced easily when sifting through a file. Your mileage may vary, both offer identical functionality (for functions).

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • Thanks for the reply. I've got a few questions. I assume get_complicated() needs to take an argument, if so, what is it's type? Also, if not helper methods, then, what methods, if any, should be private non-virtual methods of the class? And how does the anonymous namespace approach work? – Clinton Apr 05 '11 at 02:42
  • @Clinton: oups, sorry for the missing argument, I've added the anonymous namespace syntax: like static it creates a method that is only available in this source file. With regard to helper methods, it's a matter of taste. Whether you use private methods or completely hide those methods in the source file depends on your own taste/style. It's very subjective and arguable. Technically, the less there is in the header, the less often clients of the class have to recompile. But you could argue that anything that meddles directly with the class internals is logically part of the class. Up to you :) – Matthieu M. Apr 05 '11 at 06:17
0

We could forward declare something_complicated if it is a class, but it's probably a typedef, so that is out.

This is exactly what you have to do. And I don't see how being a typedef rules out a forward declaration.

Yakov Galka
  • 70,775
  • 16
  • 139
  • 220
  • 3
    Being a typedef rules out a forward declaration because you can't forward declare a typedef. – Mike Seymour Apr 04 '11 at 12:17
  • @Mike: You can forword declare whatever the typedef is and then define the typedef. – Yakov Galka Apr 06 '11 at 10:16
  • That's not necessarily possible either. If the type is nested in another class (e.g. `struct X {struct Y;}; typedef X::Y something_complicated;`), you would need the definition of the outer class, not just a forward declaration. – Mike Seymour Apr 06 '11 at 13:18
0

If you control something_complicated.hpp you could do what the standard library does: Create a something_complicated_fwd.hpp that has appropriate forward declarations, including the types that may or may not be typedefs.

Mark B
  • 95,107
  • 10
  • 109
  • 188