8

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

有没有办法造假?
是否有任何可以模拟最接近行为的 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
};

Bh (图书馆)

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

Ch (图书馆)

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

Dh (用户)

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

编辑:感谢ASH “这里不需要前向声明”。)

坏处 :-

  • 这是一种Util以某种方式识别B和的修改C
    在我看来这没有意义。
  • 现在 B 和 C 可以访问 的每个成员Util,打破任何private访问保护。
    一种方法可以只为某些成员启用朋友,但它不是那么可爱,并且在这种情况下无法使用。
  • D只是不能使用Util,但仍然可以看到它。在.
    Util_ctrl+spaceD.h

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

(编辑,接受):

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

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

4

3 回答 3

5

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

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

Lib.cpp

#include "Util.h"

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

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

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

// Forward declare Util:
class Util;

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

    // ill-formed: Util is an incomplete type
    Util mUtil;
}
于 2017-01-14T09:25:33.353 回答
3

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

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将找不到该名称的类。

于 2017-01-14T02:23:23.907 回答
2

做到这一点的一种方法是与单个中间类建立友谊,其唯一目的是提供对底层功能的访问接口。这需要一些样板文件。然后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直接访问。客户端代码也不能通过或实例调用UtilAccess成员。添加使用当前功能的额外类不需要修改or代码。ABCUtilUtilUtilAccess

这意味着您可以更严格地控​​制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); }
};

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

于 2017-01-14T03:29:51.243 回答