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 ca开发者_如何学运维n 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?
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.
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 friend
s.
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).
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_; }
};
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>
.
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.
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.
精彩评论