如何在 C++ 中伪造 "visibility of class"(不是函数)?

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

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

有什么办法可以伪造吗?
有没有macro/template/magic的C++可以模拟最接近的行为?

情况是这样

Util.h(图书馆)

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(图书馆)

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

C.h(图书馆)

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

D.h(用户)

#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.
    }
};

我糟糕的解决方案

Util的所有成员设为私有,然后声明:-

friend class B;
friend class C;

(编辑: 感谢 A.S.H "no forward declaration needed here" .)

缺点:-

(编辑)注意:这一切都是为了方便编码;防止一些错误或错误使用/更好的自动完成/更好的封装。这与反黑客或防止未经授权访问该功能无关。

(编辑,接受):

遗憾的是,我只能接受一种解决方案,所以我主观地选择了需要较少工作并提供更多灵活性的解决方案。

对于未来的读者,Preet Kukreti(& texasbruce 在评论中)和 Shmuel H.(&A.S.H是评论)也提供了很好的解决方案,值得一读。

一个可能的解决方案是将 Util 推入命名空间,并将其 typedef 放入 BC 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.
    }
};

如果 Util class 在 util.cpp 中实现,则需要将其包装在 namespace util_namespace { ... } 中。至于BC,它们的实现可以参考一个名为Util的class,没有人会更聪明。如果不启用 typedefD 将找不到该名称的 class。

实现此目的的一种方法是将单个中介 class 加为好友,其唯一目的是为底层功能提供访问接口。这需要一些样板文件。然后 AB 是 subclasses 并且因此能够使用访问接口,但不能直接在 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;
}

AB 都无法直接访问 Util。客户端代码也不能通过 AB 实例调用 UtilAccess 成员。添加使用当前 Util 功能的额外 class C 不需要修改 UtilUtilAccess 代码。

这意味着您可以更严格地控​​制 Util(特别是如果它是有状态的),使代码更易于推理,因为所有访问都是通过规定的接口进行的,而不是给 direct/accidental访问匿名代码(例如 AB)。

这需要样板文件并且不会自动传播来自 Util 的更改,但它比直接友谊更安全。

如果您不想必须 subclass,并且您很高兴每次使用 class 都有 UtilAccess 更改,您可以进行以下修改:

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); }
};

还有一些相关的解决方案(用于对class的部分进行更严格的访问控制),一个称为Attorney-Client,另一个称为PassKey,两者都在这个答案中讨论:clean C++ granular friend equivalent? (Answer: Attorney-Client Idiom)。回想起来,我认为我提出的解决方案是 Attorney-Client 习语的变体。

我认为最好的方法是根本不在 public header 中包含 Util.h

要做到这一点,#include "Util.h" 仅在执行 cpp 文件中:

Lib.cpp:

#include "Util.h"

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

通过这样做,您可以确保更改 Util.h 只会对您的库文件产生影响,而不会对库的用户产生影响。

此方法的问题是无法在您的 public header 中使用 UtilA.hB.h)。 forward-declaration 可能是这个问题的部分解决方案:

// Forward declare Util:
class Util;

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

    // ill-formed: Util is an incomplete type
    Util mUtil;
}