【问题标题】:How to fake "visibility of class" (not of functions) in C++?如何在 C++ 中伪造“类的可见性”(而不是函数的可见性)?
【发布时间】:2017-05-29 12:06:04
【问题描述】:

no feature that control visibility/accessibility of class in 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
};

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“这里不需要前向声明”。)

缺点:-

  • 这是一个修改Util以某种方式识别BC
    在我看来这没有意义。
  • 现在 B 和 C 可以访问 Util 的每个成员,打破任何 private 访问保护。
    a way to enable friend for only some members,但它不是那么可爱,而且不能用于这种情况。
  • D 只是不能使用Util,但仍然可以看到它。
    UtilD.h 中使用自动完成(例如ctrl+space)仍然是一个选择.

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

(编辑,接受):

遗憾的是,我只能接受一种解决方案,所以我主观地选择了一种工作量少、灵活性大的解决方案。

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

【问题讨论】:

  • 使Util受保护的一个封闭类的嵌套类,B和C继承该类。
  • 注意:你不需要前向声明来声明一个友元类或函数。 ("不方便:需要前向声明和好友声明")
  • @A.S.H 嘘?真的吗?看来你是对的。这对我来说是一个新知识!我会编辑问题并表扬你。谢谢!
  • 然后你可以让它成为一个文件(即“内联”),就像 Java 一样,并且只将它包含在你的库的 cpp 文件中(而不是在头文件中)。不要将其头文件导出到库的包含文件夹中。就是这样。
  • @A.S.H 不错。这也是一个很好的解决方案。我会把你添加到我的跟踪列表中。

标签: c++ visual-studio-2015 class-visibility


【解决方案1】:

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

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

Lib.cpp:

#include "Util.h"

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

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

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

// Forward declare Util:
class Util;

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

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

【讨论】:

  • unique_ptr 需要 Util 的完整定义,否则 std::default_delete 将在对不完整的类指针调用 delete 时调用未定义的行为。
  • 另请注意,在此解决方案中,Util.h 标头不需要与库一起为用户重新分发,因此您作为库作者可以确保您想要的任何有限使用,并且您图书馆的用户甚至永远不会知道它。
【解决方案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。客户端代码也不能通过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); }
};

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

【讨论】:

    【解决方案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 将找不到该名称的类。

    【讨论】:

    • 但是,任何人仍然可以使用typedef 或使用命名空间,所以Util 并没有真正隐藏,就好像它有一个更复杂的名称,还是我错过了什么?跨度>
    • 虽然它是一个肮脏的解决方案,但它很有用。它可以避免 OP 解决方案的大部分(如果不是全部)缺点。
    • C++ 的可见性/可访问性并非旨在具有军事强度的安全性。在设置了这种 typedef-aliasing 之后,尝试直接从 B 或 C 以外的任何地方意外引用 Util 类将导致编译错误。当然,完整的类使用其完整的命名空间限定名称随处可见。但如果你的目标是捕捉这种情况,没有丑陋的朋友声明,那么这是一种简单的方法。
    • 我不想要任何安全措施。我只想要编程方便(防止一些错误或错误使用/更好的 ctrl+space)。我将编辑问题。谢谢。
    • 您对“ctrl+space”的引用一定是指您正在使用的某个编译或编程工具。在构成当前 C++ 标准的 1400 多页中,没有提及该特定按键的任何特殊之处。您的问题没有任何标签来指定您正在使用的任何编程工具。如果您的问题与您使用的工具有关,这应该反映在该问题的标签中。
    猜你喜欢
    • 2013-07-06
    • 1970-01-01
    • 2020-01-01
    • 2011-08-28
    • 1970-01-01
    • 2016-02-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多