2

I am developing a C++ class library. The classes have "public" methods aimed at users, and "protected" ones supplying extra services reserved for internal use.

The classes do not derive from each other. In my current model, they need to be explicitly declared friends of each other. Is there a more compact/convenient way to achieve the same effect ?


Example:

class A
{
public:
    static int ExposedA() { return 1; }
    static int ExposedB();
    static int ExposedC();

private:
    static int Internal() { return 0; }
    friend class B;
    friend class C;
};

class B
{
public:
    static int ExposedA() { return 2 + A::Internal(); }
    static int ExposedB() { return 2; }
    static int ExposedC();

private:
    static int Internal() { return 0; }
    friend class A;
    friend class C;
};

class C
{
public:
    static int ExposedA() { return 3 + A::Internal(); }
    static int ExposedB() { return 3 + B::Internal(); }
    static int ExposedC() { return 3; }

private:
    static int Internal() { return 0; }
    friend class A;
    friend class B;
};

int A::ExposedB() { return 1 + B::Internal(); }
int A::ExposedC() { return 1 + C::Internal(); }
int B::ExposedC() { return 2 + C::Internal(); }

In practice, maintenance of the friends list is tedious, and forward references force to move the definitions out of the classes.

  • 2
    Not really relevant to your point, but why protected, why not private? – john Jul 16 '19 at 22:00
  • @john: I wanted to stress that those methods can be accessed from other classes (the qualifiers are between quotes to avoid confusion with the C++ keywords). But you can call them "private" if you prefer. –  Jul 16 '19 at 22:06
  • 1
    Typically, one solves that problem by creating some kind of `internal` namespace, where all the internal stuff is kept. If user decides to use the undocumented internal part, they do so at their own risk. – Yksisarvinen Jul 16 '19 at 22:07
  • I think I get where you are coming from, but I clipped off the protected tag because it's just going to cause confusion. – user4581301 Jul 16 '19 at 22:09
  • @Yksisarvinen: doesn't it mean that call classes must be duplicated by having a base class in the internal namespace and a user class derived from it ? –  Jul 16 '19 at 22:10
  • @user4581301: the tag should be access-model or similar. –  Jul 16 '19 at 22:11
  • A MVCE would help. It's not clear what you actually want and why the solution you're attempting is the most idiomatic one. – Walter Jul 16 '19 at 22:13
  • @Walter: I didn't say it's the most idiomatic. –  Jul 16 '19 at 22:15
  • Not a duplicate per say, but possibly related: https://stackoverflow.com/questions/6718209/when-should-you-use-friend-classes –  Jul 16 '19 at 22:41
  • Can you explain why `public` doesn't work in your situation? `public` appears to be fine in the case you provided. –  Jul 16 '19 at 22:45
  • @Chipster: no, I want to prohibit direct access to the `Internal` methods. –  Jul 16 '19 at 22:46
  • 1
    Why is it an internal method? If you need access to it outside the class, is it really an internal method? –  Jul 16 '19 at 22:47
  • 3
    I think we are looking at a XY problem here. https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem – Yunnosch Jul 16 '19 at 22:48
  • @Chipster: as sais in the question you linked to, this is a case of intentional strong coupling. –  Jul 16 '19 at 22:49
  • @Yunnosch: in fact I have been using this access model successfully for years, and it is the way I want it to be. I am after a more convenient implementation. –  Jul 16 '19 at 22:51
  • @Chipster: it is internal to the class library. Users should have no access, classes in the library should have free access. –  Jul 16 '19 at 22:57
  • @Chipster: typical case: the user function does argument validation; but for efficiency reasons, there is a corresponding internal function without validation, callable only from a class in the library. –  Jul 16 '19 at 23:08
  • 1
    When I see friends used in this way - I always default to "there's a problem with the design; because encapsulation has been broken". The most obvious thing with the example you've given, is that all 3 should share a base class; maybe B should inherit A; A certainly shouldn't be calling B. The second thing is that you're dancing with endless loops. One small slip and you'll find that A::Thing will depend on B::OtherThing; which will depend on A::Thing; and maybe 20 steps in the middle. Without a concrete example; I think all anyone can say is "go back to the drawing board". – UKMonkey Jul 16 '19 at 23:16
  • @UKMonkey: I have been using this access model successfully for years, and it is the way I want it to be. I am after a more convenient implementation. –  Jul 17 '19 at 06:05
  • @UKMonkey: please don't let's play on words. I am looking for tips for easier implementation, not for sterile discussions. –  Jul 17 '19 at 09:24

1 Answers1

0

I reckon the short answer is "no".

In the comments (for anyone reading this in the future), you identified your problem as "a case of intentional strong coupling" as stated in an answer to this question (emphasis mine):

In general, friend classes are useful in designs where there is intentional strong coupling: you need to have a special relationship between two classes. More specifically, one class needs access to another classes's internals and you don't want to grant access to everyone by using the public access specifier.

The answer goes on to state:

The rule of thumb: If public is too weak and private is too strong, you need some form of selected access: either protected or friend (the package access specifier in Java serves the same kind of role).

In other words, if public doesn't work for you, and private doesn't work for you, you are left protected and friend. If you also can't use protected, then you are stuck with friend by the process of elimination. You can't change the language (unless you want to submit a paper to the committee, but that's a whole other topic entirely). You are left to juggle the these access specifiers around the best way you can.

To the question of which way is the best way to use these specifiers, I have no answer. It's nearly impossible for me to know what juggle combination will work best for your situation. So far, your solution seems to be the best. Nothing off the top of my head seems better, given your constraints.


it is internal to the class library. Users should have no access, libraries in the class should have mutual access.

If I'm understanding you correctly (and I may not be), I will say that you do have the (untested) option of having dual headers: one for you, and one for the end user. In your header, you can make the function public like this:

// YouB.h
class B
{
public:
    static int ExposedA() { return 2 + A::Internal(); }
    static int ExposedB() { return 2; }
    static int ExposedC();
    // This is a public function. Your compiler should have no problem with other classes using it
    static int Internal() { return 0; }
};

Then, you have a different header for your users:

// UserB.h
// YouB.h
class B
{
public:
    static int ExposedA() { return 2 + A::Internal(); }
    static int ExposedB() { return 2; }
    static int ExposedC();
private:
    // This is a now a private or protected function.
    // I haven't tested this, so I don't know if the other access thing will work,
    // but since it's solely an internal function, commenting it out or removing
    // it completely should work too
    static int Internal() { return 0; }
};
Justin
  • 24,288
  • 12
  • 92
  • 142
  • Interesting. To avoid a double maintenance problem, better let the private specifier as conditionally compiled. Indeed difficult to predict if this is (and will remain) harmless. –  Jul 17 '19 at 09:22
  • Is this (having two contradicting definitions) legal? doesn't this call for trouble? – Walter Jul 19 '19 at 11:15
  • @Walter If the lib is a static lib, then you may be able to get away with it. That said, I'll be the first to admit that "get away with it" and "good idea" are two different things. I just figured I'd mention it just in case the OP found it useful. –  Jul 19 '19 at 11:38