Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to fake "visibility of class" (not of functions) in C++?

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.

like image 589
javaLover Avatar asked Jan 14 '17 02:01

javaLover


3 Answers

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.

like image 25
Sam Varshavchik Avatar answered Oct 29 '22 13:10

Sam Varshavchik


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;
}
like image 58
Shmuel H. Avatar answered Oct 29 '22 13:10

Shmuel H.


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.

like image 37
Preet Kukreti Avatar answered Oct 29 '22 13:10

Preet Kukreti