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

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

本文关键字:功能 class visibility C++ 伪造 of      更新时间:2023-10-16

没有功能可以控制C++中类的可见性/可访问性。

有什么办法可以伪造它吗?
是否有任何宏/模板/魔术C++可以模拟最接近的行为?

这是情况

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

#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"这里不需要前瞻声明"。

缺点:-

  • 以某种方式识别BC是一种修改Util
    在我看来,这没有意义。
  • 现在B和C可以访问Util的每个成员,打破任何private访问卫士。
    有一种方法可以只为某些成员启用朋友,但它不是那么可爱,在这种情况下无法使用。
  • D只是不能使用Util,但仍然可以看到它。
    D.h中使用自动完成(例如ctrl+空格)时,Util仍然是一种选择。

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

(编辑,接受):

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

对于未来的读者,Preet Kukreti(评论中的德克萨斯布鲁斯)和ShmuelH.(评论中的A.S.H)也提供了值得一读的良好解决方案。

我认为最好的方法是根本不在公共标头中包含Util.h

为此,仅在实现cpp文件中#include "Util.h"

Lib.cpp

#include "Util.h"
void A::publicFunction() 
{
Util::calculate();
}

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

这种方法的问题在于无法在公共标头中使用Util(A.hB.h)。 前向声明可能是此问题的部分解决方案:

// Forward declare Util:
class Util;
class A {
private:
// OK;
Util *mUtil;
// ill-formed: Util is an incomplete type
Util mUtil;
}

一种可能的解决方案是将Util推入命名空间,并将其typedefBC类中:

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类是用util.cpp实现的,这就需要把它包装在一个namespace util_namespace { ... }里。就BC而言,它们的实现可以引用一个名为Util的类,没有人会更聪明。如果没有启用typedefD将无法找到该名称的类。

实现此目的的一种方法是与单个中间类交朋友,该中介类的唯一目的是提供对基础功能的访问接口。这需要一些样板。然后AB是子类,因此能够使用访问接口,但不能直接使用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功能的额外类C不需要修改UtilUtilAccess代码。

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

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

如果您不想必须子类,并且您很高兴为每个使用类进行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); }
};

还有一些相关的解决方案(用于对类的各个部分进行更严格的访问控制),一个称为律师-客户,另一个称为PassKey,这两个解决方案都在此答案中讨论:干净C++粒度的朋友等价物?(答案:律师-客户成语) .回想起来,我认为我提出的解决方案是律师-客户习语的变体。