如何在 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" .)
缺点:-
- 这是一个修改
Util
以某种方式识别 B
和 C
。
我认为这没有意义。
- 现在 B 和 C 可以访问
Util
的每个成员,打破任何 private
门禁。
有a way to enable friend for only some members,但不是很可爱,不能用于这种情况。
D
就是不能用Util
,但是还是可以看的
在 D.h
中使用自动完成(例如 ctrl+space)时,Util
仍然是一个选择。
(编辑)注意:这一切都是为了方便编码;防止一些错误或错误使用/更好的自动完成/更好的封装。这与反黑客或防止未经授权访问该功能无关。
(编辑,接受):
遗憾的是,我只能接受一种解决方案,所以我主观地选择了需要较少工作并提供更多灵活性的解决方案。
对于未来的读者,Preet Kukreti(& texasbruce 在评论中)和 Shmuel H.(&A.S.H是评论)也提供了很好的解决方案,值得一读。
一个可能的解决方案是将 Util
推入命名空间,并将其 typedef
放入 B
和 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.
}
};
如果 Util
class 在 util.cpp
中实现,则需要将其包装在 namespace util_namespace { ... }
中。至于B
和C
,它们的实现可以参考一个名为Util
的class,没有人会更聪明。如果不启用 typedef
,D
将找不到该名称的 class。
实现此目的的一种方法是将单个中介 class 加为好友,其唯一目的是为底层功能提供访问接口。这需要一些样板文件。然后 A
和 B
是 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;
}
A
或 B
都无法直接访问 Util
。客户端代码也不能通过 A
或 B
实例调用 UtilAccess
成员。添加使用当前 Util
功能的额外 class C
不需要修改 Util
或 UtilAccess
代码。
这意味着您可以更严格地控制 Util
(特别是如果它是有状态的),使代码更易于推理,因为所有访问都是通过规定的接口进行的,而不是给 direct/accidental访问匿名代码(例如 A
和 B
)。
这需要样板文件并且不会自动传播来自 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 中使用 Util
(A.h
、B.h
)。 forward-declaration 可能是这个问题的部分解决方案:
// Forward declare Util:
class Util;
class A {
private:
// OK;
Util *mUtil;
// ill-formed: Util is an incomplete type
Util mUtil;
}
有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" .)
缺点:-
- 这是一个修改
Util
以某种方式识别B
和C
。
我认为这没有意义。 - 现在 B 和 C 可以访问
Util
的每个成员,打破任何private
门禁。
有a way to enable friend for only some members,但不是很可爱,不能用于这种情况。 D
就是不能用Util
,但是还是可以看的
在D.h
中使用自动完成(例如 ctrl+space)时,Util
仍然是一个选择。
(编辑)注意:这一切都是为了方便编码;防止一些错误或错误使用/更好的自动完成/更好的封装。这与反黑客或防止未经授权访问该功能无关。
(编辑,接受):
遗憾的是,我只能接受一种解决方案,所以我主观地选择了需要较少工作并提供更多灵活性的解决方案。
对于未来的读者,Preet Kukreti(& texasbruce 在评论中)和 Shmuel H.(&A.S.H是评论)也提供了很好的解决方案,值得一读。
一个可能的解决方案是将 Util
推入命名空间,并将其 typedef
放入 B
和 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.
}
};
如果 Util
class 在 util.cpp
中实现,则需要将其包装在 namespace util_namespace { ... }
中。至于B
和C
,它们的实现可以参考一个名为Util
的class,没有人会更聪明。如果不启用 typedef
,D
将找不到该名称的 class。
实现此目的的一种方法是将单个中介 class 加为好友,其唯一目的是为底层功能提供访问接口。这需要一些样板文件。然后 A
和 B
是 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;
}
A
或 B
都无法直接访问 Util
。客户端代码也不能通过 A
或 B
实例调用 UtilAccess
成员。添加使用当前 Util
功能的额外 class C
不需要修改 Util
或 UtilAccess
代码。
这意味着您可以更严格地控制 Util
(特别是如果它是有状态的),使代码更易于推理,因为所有访问都是通过规定的接口进行的,而不是给 direct/accidental访问匿名代码(例如 A
和 B
)。
这需要样板文件并且不会自动传播来自 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 中使用 Util
(A.h
、B.h
)。 forward-declaration 可能是这个问题的部分解决方案:
// Forward declare Util:
class Util;
class A {
private:
// OK;
Util *mUtil;
// ill-formed: Util is an incomplete type
Util mUtil;
}