8

There is no feature that control visibility/accessibility of class in C++.

Is there any way to fake it?
Are there any macro/template/magic of C++ that can simulate the closest behavior?

Here is the situation

Util.h (library)

class Util{   
    //note: by design, this Util is useful only for B and C
    //Other classes should not even see "Util"
    public: static void calculate(); //implementation in Util.cpp
};

B.h (library)

#include "Util.h"
class B{ /* ... complex thing */  };

C.h (library)

#include "Util.h"
class C{ /* ... complex thing */  };

D.h (user)

#include "B.h"    //<--- Purpose of #include is to access "B", but not "Util"
class D{ 
    public: static void a(){
        Util::calculate();   //<--- should compile error     
        //When ctrl+space, I should not see "Util" as a choice.
    }
};

My poor solution

Make all member of Util to be private, then declare :-

friend class B;
friend class C;

(Edit: Thank A.S.H for "no forward declaration needed here".)

Disadvantage :-

  • It is a modifying Util to somehow recognize B and C.
    It doesn't make sense in my opinion.
  • Now B and C can access every member of Util, break any private access guard.
    There is a way to enable friend for only some members but it is not so cute, and unusable for this case.
  • D just can't use Util, but can still see it.
    Util is still a choice when use auto-complete (e.g. ctrl+space) in D.h.

(Edit) Note: It is all about convenience for coding; to prevent some bug or bad usage / better auto-completion / better encapsulation. This is not about anti-hacking, or prevent unauthorized access to the function.

(Edit, accepted):

Sadly, I can accept only one solution, so I subjectively picked the one that requires less work and provide much flexibility.

To future readers, Preet Kukreti (& texasbruce in comment) and Shmuel H. (& A.S.H is comment) has also provided good solutions that worth reading.

Community
  • 1
  • 1
javaLover
  • 6,347
  • 2
  • 22
  • 67
  • 2
    Make Util protected nested class of an enclosure class, and B and C inherit that class. – SwiftMango Jan 14 '17 at 02:14
  • 1
    Note: You don't need forward-declaration to declare a friend class or function. ( *"Not convenient : need forward declaration and friend declaration"* ) – A.S.H Jan 14 '17 at 02:24
  • 1
    @A.S.H Hurr? Really? It seems that you are correct. That is a new knowledge for me! I will edit the question and praise you. Thank! – javaLover Jan 14 '17 at 02:32
  • @javaLover are you looking for something like "package-scope visibility" of Java? if so, you *can* achieve that in C++. – A.S.H Jan 14 '17 at 02:55
  • @A.S.H Yes, in class level. – javaLover Jan 14 '17 at 03:11
  • 1
    Then you can make it one-file (i.e. "inlined"), just like Java, and include it only in the cpp files of your library (not in the header files). dont export its header file in the library's include folder. That's it. – A.S.H Jan 14 '17 at 03:18
  • 1
    @A.S.H Nice. It is also a good solution. I will add you to my stalking list. – javaLover Jan 14 '17 at 03:20

3 Answers3

5

I think that the best way is not to include Util.h in a public header at all.

To do that, #include "Util.h" only in the implementation cpp file:

Lib.cpp:

#include "Util.h"

void A::publicFunction() 
{
    Util::calculate();
}

By doing that, you make sure that changing Util.h would make a difference only in your library files and not in the library's users.

The problem with this approach is that would not be able to use Util in your public headers (A.h, B.h). forward-declaration might be a partial solution for this problem:

// Forward declare Util:
class Util;

class A {
private:
    // OK;
    Util *mUtil;

    // ill-formed: Util is an incomplete type
    Util mUtil;
}
Shmuel H.
  • 2,348
  • 1
  • 16
  • 29
  • 1
    The unique_ptr needs the full definition of Util, otherwise std::default_delete will invoke undefined behaviour when calling delete on an incomplete class pointer. – rubenvb Jan 15 '17 at 09:10
  • 2
    Also note that in this solution, the Util.h header doesn't need to get redistributed with the library for users, and so you as the library author can ensure whatever limited use you want, and a user of your library will never even know about it. – rubenvb Jan 15 '17 at 09:11
3

One possible solution would be to shove Util into a namespace, and typedef it inside the B and C classes:

namespace util_namespace {

    class Util{
    public:
        static void calculate(); //implementation in Util.cpp
    };
};

class B {

    typedef util_namespace::Util Util;

public:

    void foo()
    {
        Util::calculate(); // Works
    }
};

class C {

    typedef util_namespace::Util Util;

public:

    void foo()
    {
        Util::calculate(); // Works
    }
};

class D {

public:

    void foo()
    {
        Util::calculate(); // This will fail.
    }
};

If the Util class is implemented in util.cpp, this would require wrapping it inside a namespace util_namespace { ... }. As far as B and C are concerned, their implementation can refer to a class named Util, and nobody would be the wiser. Without the enabling typedef, D will not find a class by that name.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • But, anyone still can do the `typedef` or use the namespace, so `Util` is not really hidden, just as if it has a more complex name, or did I miss something? – A.S.H Jan 14 '17 at 02:37
  • 1
    Although it is a dirty solution, it is useful. It can avoid most (if not all) disadvantages of OP's solution. – javaLover Jan 14 '17 at 02:39
  • 2
    C++'s visibility/accessibility is not designed to have military-strength security. After this kind of a typedef-aliasing is set up, an attempt to accidentally reference the `Util` class directly from any place other than B or C will result in a compilation error. Of course, the full class is visible everywhere using its full namespace-qualified name. but if your goal is to catch this kind of a situation, without ugly friend declarations then this is a simple way to do it. – Sam Varshavchik Jan 14 '17 at 02:51
  • 1
    I don't want any security. I want only convenience for programming (prevent some bug or bad usage / better ctrl+space). I will edit the question. Thank. – javaLover Jan 14 '17 at 03:13
  • Your reference to "ctrl+space" must be referring to some compilation or programming tool that you're using. In the 1400+ pages that make up the current C++ standard, there's no mention of anything special about that particular keypress. Your question does not have any tags that specify whatever programming tool you're using. If your question is related to a tool that you use, this should be reflected in this question's tags. – Sam Varshavchik Jan 14 '17 at 03:19
2

One way to do this is by friending a single intermediary class whose sole purpose is to provide an access interface to the underlying functionality. This requires a bit of boilerplate. Then A and B are subclasses and hence are able to use the access interface, but not anything directly in Utils:

class Util
{
private:
    // private everything.
    static int utilFunc1(int arg) { return arg + 1; }
    static int utilFunc2(int arg) { return arg + 2; }

    friend class UtilAccess;
};

class UtilAccess
{
protected:
    int doUtilFunc1(int arg) { return Util::utilFunc1(arg); }
    int doUtilFunc2(int arg) { return Util::utilFunc2(arg); }
};

class A : private UtilAccess
{
public:
    int doA(int arg) { return doUtilFunc1(arg); }
};

class B : private UtilAccess
{
public:
    int doB(int arg) { return doUtilFunc2(arg); }
};

int main()
{
    A a;
    const int x = a.doA(0); // 1
    B b;
    const int y = b.doB(0); // 2
    return 0;
}

Neither A or B have access to Util directly. Client code cannot call UtilAccess members via A or B instances either. Adding an extra class C that uses the current Util functionality will not require modification to the Util or UtilAccess code.

It means that you have tighter control of Util (especially if it is stateful), keeping the code easier to reason about since all access is via a prescribed interface, instead of giving direct/accidental access to anonymous code (e.g. A and B).

This requires boilerplate and doesn't automatically propagate changes from Util, however it is a safer pattern than direct friendship.

If you do not want to have to subclass, and you are happy to have UtilAccess change for every using class, you could make the following modifications:

class UtilAccess
{
protected:
    static int doUtilFunc1(int arg) { return Util::utilFunc1(arg); }
    static int doUtilFunc2(int arg) { return Util::utilFunc2(arg); }

    friend class A;
    friend class B;
};

class A
{
public:
    int doA(int arg) { return UtilAccess::doUtilFunc1(arg); }
};

class B
{
public:
    int doB(int arg) { return UtilAccess::doUtilFunc2(arg); }
};

There are also some related solutions (for tighter access control to parts of a class), one called Attorney-Client and the other called PassKey, both are discussed in this answer: clean C++ granular friend equivalent? (Answer: Attorney-Client Idiom) . In retrospect, I think the solution I have presented is a variation of the Attorney-Client idiom.

Community
  • 1
  • 1
Preet Kukreti
  • 8,417
  • 28
  • 36