允许"friend"类仅访问某些私有成员

Allowing a "friend" class to access only some private members

本文关键字:成员 访问 friend 允许      更新时间:2023-10-16

假设我有三个C++类FooA、FooB和FooC。

FooA有一个名为Hello的成员函数,我想在FooB类中调用这个函数,但我不希望FooC类能够调用它。实现这一点的最好方法是将FooB声明为FooA的朋友类。但只要我这样做,所有FooA的私人和受保护成员都将被曝光,这对我来说是完全不可接受的。

所以,我想知道C++(03或11(中是否有比friend类更好的机制可以解决这个困境。

我认为,如果以下语法是可能的,那就太好了:

class FooA
{
private friend class FooB:
    void Hello();
    void Hello2();
private:
    void Hello3();
    int m_iData;
};
class FooB
{
    void fun()
    {
        FooA objA;
        objA.Hello()  // right
        objA.Hello2() // right
        objA.Hello3() // compile error
        ojbA.m_iData = 0; // compile error
    }
};
class FooC
{
    void fun()
    {
        FooA objA;
        objA.Hello()  // compile error
        objA.Hello2() // compile error
        objA.Hello3() // compile error
        ojbA.m_iData = 0; // compile error
    }
};

我认为您可以在这里使用Attorney Client。

在您的情况下,示例应该是这样的

class FooA
{
private:
    void Hello();
    void Hello2();
    void Hello3();
    int m_iData;
    friend class Client;
};
class Client
{
private:
   static void Hello(FooA& obj)
   {
      obj.Hello();
   }
   static void Hello2(FooA& obj)
   {
      obj.Hello2();
   }
   friend class FooB;
};
class FooB
{
    void fun()
    {
        FooA objA;
        Client::Hello(objA);  // right
        Client::Hello2(objA); // right
        //objA.Hello3() // compile error
        //ojbA.m_iData = 0; // compile error
    }
};
class FooC
{
    void fun()
    {
        /*FooA objA;
        objA.Hello()  // compile error
        objA.Hello2() // compile error
        objA.Hello3() // compile error
        ojbA.m_iData = 0; // compile error*/
    }
};

没有什么可以让一个类成为一个特定函数的朋友,但你可以用私有构造函数让FooB成为一个"键"类的朋友,然后让FooA::Hello将该类作为一个被忽略的参数。FooC将无法提供参数,因此无法调用Hello:

这种面向密钥的访问保护模式是众所周知的习惯用法吗?

您可以通过从接口类继承类的接口,将类的接口部分公开给指定的客户端。

class FooA_for_FooB
{
public:
    virtual void Hello() = 0;
    virtual void Hello2() = 0;
};
class FooA : public FooA_for_FooB
{
private: /* make them private */
    void Hello() override;
    void Hello2() override;
private:
    void Hello3();
    int m_iData;
};
class FooB
{
    void fun()
    {
        FooA objA;
        FooA_for_FooB &r = objA;
        r.Hello()  // right
        r.Hello2() // right
        objA.Hello3() // compile error
        objA.m_iData = 0; // compile error
    }
};
class FooC
{
    void fun()
    {
        FooA objA;
        objA.Hello()  // compile error
        objA.Hello2() // compile error
        objA.Hello3() // compile error
        objA.m_iData = 0; // compile error
    }
};

这里,通过基类FooA_for_FooB来增强访问控制。通过FooA_for_FooB类型的引用,FooB可以访问在FooA_for_FooB中定义的成员。但是,FooC无法访问这些成员,因为它们在FooA中已被覆盖为私有成员。您的目的可以通过不使用FooC中的类型FooA_for_FooB或除FooB之外的任何其他地方来实现,这些地方可以不用太多注意就可以保留。

这种方法不需要friend,使事情变得简单。

类似的事情可以通过使基类中的所有内容都是私有的,并选择性地将派生类中的一些成员包装和公开为公共成员来完成。不过,这种方法有时可能需要丑陋的悲观情绪。(因为基类将成为整个程序中的"货币"。(

否,这并不是真正的限制。在我看来,局限性在于friend——一种针对设计缺陷进行黑客攻击的钝器——首先存在。

您的类FooA不需要知道FooBFooC以及"哪一个应该能够使用它"。它应该有一个公共接口,不在乎谁可以使用它。这就是接口的重点!调用该接口中的函数应始终使FooA处于良好、安全、愉快、一致的状态。

如果你担心你可能无意中从某个地方使用了FooA接口,那么,不要这么做;C++不是一种适合防止此类用户错误的语言。在这种情况下,您的测试覆盖率应该足够了。

严格地说,我相信你可以通过一些极其复杂的"设计模式"获得你想要的功能,但老实说,我不会介意。

如果这是程序设计的语义问题,那么我礼貌地建议您的设计有缺陷。

最安全的解决方案是使用另一个类作为两个类的"中间人",而不是将其中一个作为friend.。@ForEverR在回答中建议了一种方法,但您也可以搜索代理类和其他可应用的设计模式。

您需要继承。试试这个:

// _ClassA.h
class _ClassA
{
  friend class ClassA;
private:
  //all your private methods here, accessible only from ClassA and _ClassA.
}
// ClassA.h
class ClassA: _ClassA
{
  friend class ClassB;
private:
  //all_your_methods
}

通过这种方式,您可以:CCD_ 24是唯一能够使用CCD_。ClassB无法访问私有的_ClassA方法。

您可以在基类中隐藏私有成员,然后使FooA成为该基类的子级和朋友。

// allows us to hide private members from friends of FooA,
// but still allows FooA itself to access them.
class PrivateFooA
{
private:
    friend class FooA;
    
    // only allow FooA to derive from this class
    PrivateFooA() {};
    
    // hidden from friends of FooA
    void Hello3();
    int m_iData;
};
// this class hides some of its private members from friend classes
class FooA : public PrivateFooA
{
private:
    // give FooB access to private methods
    friend class FooB;
    
    void Hello();
    void Hello2();
};
class FooB
{
    void fun()
    {
        FooA objA;
        objA.Hello();  // right
        objA.Hello2(); // right
        objA.Hello3(); // compile error
        ojbA.m_iData = 0; // compile error
    }
};
class FooC
{
    void fun()
    {
        FooA objA;
        objA.Hello();  // compile error
        objA.Hello2(); // compile error
        objA.Hello3(); // compile error
        ojbA.m_iData = 0; // compile error
    }
};

你想向FooB隐藏的任何东西都可以放入PrivateFooA(必须是私人成员(,其他所有东西都可以直接放入FooA。FooA将能够访问PrivateFooA中的所有内容,就像它自己的成员一样。

这更多的是对user3737631答案的扩展,但我认为它值得发布,因为它包括了OP中的类、PrivateFooA中的私有构造函数,以及一些我认为会有所帮助的其他注释。

friend的整个想法是将您的类公开给朋友。

有两种方法可以更具体地说明你暴露的内容:

  1. FooA继承,这样只公开受保护的和公共的方法。

  2. 只与某个方法交朋友,这样只有该方法才能访问:

 friend void FooB::fun();

我最近不得不这样做,我不喜欢这些解决方案将类类型挂在当前命名空间中而没有任何目的的方式。如果你真的只是想让这个功能对一个类可用,那么我会使用一个不同于上面提到的模式。

class Safety {
protected:
   std::string _Text="";
public:
   Safety(const std::string& initial_text) {
      _Text=initial_text;
   }
   void Print(const std::string& test) {
      std::cout<<test<<" Value: "<<_Text<<std::endl;
   }
};
class SafetyManager {
protected:
   // Use a nested class to provide any additional functionality to 
   // Safety that you want with protected level access. By declaring
   // it here this code only belongs to this class. Also, this method
   // doesn't require Safety to inherit from anything so you're only
   // adding weight for the functionality you need when you need it.
   // You need to be careful about how this class handles this object
   // since it is really a Safety cast to a _Safety. You can't really 
   // add member data to this class but static data is ok.
   class _Safety : Safety {
   public:
      void SetSafetyText(const std::string& new_text) {
         _Text=std::string(new_text);
      }
   };
   
public:
   static void SetSafetyText(Safety* obj, const std::string& new_text) {
      if(obj==nullptr) throw "Bad pointer.";
      _Safety& iobj=*(_Safety*)obj;
      iobj.SetSafetyText(new_text);
   }
};

然后在main(或其他任何地方(中,您不能通过Safety修改_Text,但可以通过SafetyManager(或其子代(修改。

#include "Safety.h"
int main() {
   Safety t("Hello World!");
   t.Print("Initial");
   SafetyManager::SetSafetyText(&t, "Brave New World!");
   t.Print("Modified");
/*
   t._Text;                         // not accessible
   Safety::SetSafetyText(&t, "ERR");// doesn't exist
   t.SetSafetyText(&t, "ERR");      // doesn't exist
   _Safety _safety;                 // not accessible
   SafetyManager::_Safety _safety;  // not accessible
*/
}

有人会说,这遵循了比朋友类更好的OOP实践,因为它更好地封装了混乱的部分,并且不会将任何东西传递到继承的安全链中。您也根本不需要为这种技术修改安全类,使其更加模块化。这可能就是为什么许多较新的语言允许嵌套类,但几乎没有其他语言借用了友元概念的原因,尽管只是添加了仅对单个类可用的功能(如果安全性标记为最终或将其代码的重要部分标记为私有,则不起作用(。