2

e.g.

// Implementation.
struct PrivatePoint {
  void SomePrivateMethod();

  double x;
  double y;
}

struct Point : private PrivatePoint {
  double DistanceTo(const Point& other) const;
}

This seems similar to the Pimpl idiom. This has two advantages that I really like:

  1. SomePrivateMethod is testable. If SomePrivateMethod were instead declared as private in Point, you wouldn't be able to call it from tests. If you declared it as public or protected in Point, tests would be able to call it, but so would regular users of Point.
  2. Accessing private data is easier to read and write compared to how you'd do it in the Pimpl idiom, because you don't have to go through a pointer e.g.

.

Point::DistanceTo(const Point& other) {
  SomePrivateMethod();

  double dx = other.x - x;
  double dy = other.y - y;
  return sqrt(dx * dx + dy * dy);
}

vs.

Point::DistanceTo(const Point& other) {
  ptr->SomePrivateMethod();

  double dx = other.ptr->x - ptr->x;
  double dy = other.ptr->y - ptr->y;
  return sqrt(dx * dx + dy * dy);
}
Brady
  • 10,207
  • 2
  • 20
  • 59
allyourcode
  • 21,871
  • 18
  • 78
  • 106

3 Answers3

2

Your suggestion has some drawbacks....

The users may couple themselves to the "private" class.

The pimpl idiom's primary purpose is as a compilation firewall, allowing the private members to be specified in the implementation file, and therefore to be changed without touching the header and necessitating / triggering client recompilation, as distinct from just relinking, which is faster, and may not even require any actions involving the client app if the update is to a dynamically loaded library. You lose these benefits.

SomePrivateMethod is testable. If SomePrivateMethod were instead declared as private in Point, you wouldn't be able to call it from tests. If you declared it as public or protected in Point, tests would be able to call it, but so would regular users of Point.

There are other convenient if hackish options: for example - you can declare friendship with testing code, or use the preprocessor to build in a testing mode that exposes data.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • Re coupling: I don't think that would be a problem in practice, because PrivatePoint is clearly marked as "do not use". – allyourcode Sep 21 '12 at 02:29
  • re firewall: Is there a way to do Pimpl that is easy to read and write? Fast builds are good, but so is readable code :/ – allyourcode Sep 21 '12 at 02:40
  • @allyourcode: What is your concern with the reading and writing of the pimpl idiom? – David Rodríguez - dribeas Sep 21 '12 at 03:26
  • @DavidRodríguez-dribeas It just isn't as straightforward. Take a look at the two examples in my question. Pimpl (by design) introduces an unnecessary (but possibly beneficial) layer of indirection; instead of accessing data directly, you have to go through a pointer. – allyourcode Sep 21 '12 at 04:18
  • @allyourcode: re "do not use" - a lazy client trying to get something done quick won't always care. Re pImpl hassle: the pointed-to implementation class doesn't have to be restricted to just data members - some of the implementation code can be put directly in there too, where it doesn't need to continually use a pointer. The exposed outer class does need to use a pointer (other options are possible but even less convenient). – Tony Delroy Sep 21 '12 at 04:48
  • @allyourcode: The whole reason for the pimpl is having that extra layer of indirection. Without it, in your case for example, there is little reason to provide a different type that is going to make your interface more cumbersome and provide little to no advantages (other than being able to test the members that would otherwise be private. Any change in the *private* type has the same impact on users as if you changed a private member in the original class. Going back to the original point: *what is your concern with the pimpl, the extra indirection? Performance? Use of arrow operator? – David Rodríguez - dribeas Sep 21 '12 at 12:16
  • @DavidRodríguez-dribeas You seem to think testability is not that important. I very disagree with that. re problem with extra indirection: It's not just the arrow operator. You have to mention the pointer too. I think most people looking at my two examples would MUCH prefer the non-Pimpl version with good reason: they'd much rather look at something directly vs. through a chicken wire fence. – allyourcode Sep 21 '12 at 21:15
  • @allyourcode: I don't believe that testability is unimportant, it is very important, the question is whether testing the implementation is important or testing the interface is the goal. What I am not sure I made clear enough is that your solution has the same physical dependencies than having the private members directly in the *public* class. The only advantage is the possibility of testing *private* functions. Tests should provide 100% coverage of the interface of the object (`public`, `protected` and `virtual`). [...] – David Rodríguez - dribeas Sep 22 '12 at 00:27
  • [...] writing test drivers for the *private* (i.e. implementation) functions is taking away man-hours from the public interface. If you refactor the internal functions you will have then to invest even more time on testing the internals. All that time can be better dedicated to better coverage of the *interface* that are your contracts with all the rest of the code. That being said, the pimpl idiom has the advantage of breaking physical dependencies which is almost orthogonal to testing. Of course, in any of the two options (yours/pimpl) it will be easier to test, but that is just helping. – David Rodríguez - dribeas Sep 22 '12 at 00:34
  • @DavidRodríguez-dribeas: "test drivers for [private] functions is taking away man-hours from the public interface" - will have to disagree there... implementation can be complex and layered enough to warrant direct testing at strategic points, especially if going through the API creates state that can't be cleared to test other use cases. Whitebox test cases are valuable but even via public API, at least logically coupled to implementation anyway. There's definitely an art to allocating time and effort effectively for testing, but IMHO public vs private isn't quite the right divide.... – Tony Delroy Sep 24 '12 at 09:58
1

Private inheritance is very similar to composition (and pimpl) but has some drawbacks to consider:

  1. You need to make PrivatePoint definition visible in a public header. This introduces compile time dependency on PrivatePoint and requires clients of Point to recompile every time PrivatePoint is changed. With pimpl this is not the case, it is enough to forward declare PrivatePoint like this: struct PrivatePoint;

  2. Enapsulation is weeker. With private inheritance, clients that extend Point are be able to implement their own version of SomePrivateMethod (C++ allows to overwrite private virtual methods). This is not a problem in your example, but would be if SomePrivateMethod was declared virtual

If you used pimpl, you could also easily unit test PrivatePoint.

Jan Wrobel
  • 6,969
  • 3
  • 37
  • 53
  • Thanks for mentioning that private virtual member functions can be overridden. That seems wrong, but then again, we are talking about C++ :P. – allyourcode Sep 21 '12 at 21:05
0

no, as anyway you have to publish header file that declares the base class. the private inheritance protect you or your users from unwanted access to the data/methods.

Serge
  • 6,088
  • 17
  • 27
  • How would users access data that I don't want them to? My library would never instantiate PrivatePoint; it only exists to be the base class for Point. If a user instantiates PrivatePoint, then future releases of the library will break him. But that's his own fault, because that is a very obvious mistake (imho). Even if he ignores the comments in all caps that say NEVER INSTANTIATE THIS CLASS, the name should make that pretty obvious by itself. – allyourcode Sep 21 '12 at 02:45
  • 1
    @allyourcode: Assuming that people will not use a type that is offered in the interface is assuming quite a lot. History has proven that users tend to do otherwise, ask Microsoft and users that exploited undocumented fields in inaccessible structures of the operating system and forced Microsoft to provide backwards compatibility for things that were not only not meant to be used, but not even accessible through the interfaces offered by the OS – David Rodríguez - dribeas Sep 21 '12 at 03:30
  • @allyourcode then tell me please what's the difference between your case and this one: struct Point { private: void SomePrivateMethod(); double x; double y; public: double DistanceTo(const Point& other); } – Serge Sep 21 '12 at 20:31
  • @Allyourcode: Anyway the _implementation details_ remains hidden. As I mentioned earlier, you have to provide an end-user of your library with the header file that declares your base class as otherwise user will not be able to even compile his code that uses your Point class. If you really want to expose the methods only then you need to implement some mapping to map the actual pointers to the instances that user can see to your internally allocated objects. Do you need this complexity? What kind of know-how you are going to protect? How to turn Pb into Au? ;) – Serge Sep 21 '12 at 20:33
  • @Serge re your first comment: Having PrivatePoint, and its member function SomePrivateMethod allows me to call SomePrivateMethod in tests. Pimpl also allows this, but it has the disadvantage of making Point code more awkward. – allyourcode Sep 21 '12 at 21:01
  • @allyourcode then I conclude that this is the matter of taste :) sorry for long discussion) – Serge Sep 21 '12 at 21:21
  • @Serge Not at all. Very interesting :) – allyourcode Sep 21 '12 at 21:42