40

假设我有三个 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
    }
};
4

8 回答 8

31

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

这种面向密钥的访问保护模式是已知的惯用语吗?

于 2013-04-17T08:53:57.343 回答
30

我认为您可以在这里使用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*/
    }
};
于 2013-04-17T08:54:49.137 回答
3

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

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_FooBFooB可以访问其中定义的成员FooA_for_FooB。但是,FooC无法访问这些成员,因为它们已被覆盖为FooA. 你的目的可以不使用 inside 的类型FooA_for_FooB,也可以不使用FooC除 以外的任何地方FooB,不用太注意就可以保留。

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

类似的事情可以通过将基类中的所有内容设为私有,并在派生类中选择性地将一些成员封装并公开为公有来完成。不过,这种方法有时可能需要丑陋的沮丧。(因为基类将成为整个程序中的“货币”。)

于 2016-01-27T13:02:52.787 回答
2

不,这并不是真正的限制。在我看来,限制是friend——一种绕过设计缺陷的钝器——首先存在。

你的班级FooA不知道FooBFooC“哪一个应该能够使用它”。它应该有一个公共接口,而不在乎谁可以使用它。这就是界面的重点!在该接口中调用函数应该始终保持良好FooA、安全、快乐、一致的状态。

如果您担心您可能会不小心FooA从您不想使用的地方使用该界面,那么请不要那样做;C++ 不是一种适合防止此类用户错误的语言。在这种情况下,您的测试覆盖率应该足够了。

严格来说,我相信你可以通过一些极其复杂的“设计模式”获得你想要的功能,但老实说,我不会打扰。

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

于 2013-04-17T08:55:21.003 回答
1

最安全的解决方案是使用另一个类作为两个类的“中间人”,而不是让其中一个类成为friend.@ForEveR 的答案中建议的一种方法,但您也可以对代理进行一些搜索类和其他可以应用的设计模式。

于 2013-04-17T10:01:47.697 回答
0

你需要继承。尝试这个:

// _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
}

这样你就有: ClassB是唯一能够使用ClassA. ClassB无法访问_ClassA私有的方法。

于 2014-06-13T12:20:21.053 回答
0

整个想法friend是将您的课程暴露给朋友。

有两种方法可以让您更具体地了解您所公开的内容:

  1. 继承自FooA,这样只会暴露受保护的和公共的方法。

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

.

 friend void FooB::fun();
于 2013-04-17T08:57:30.990 回答
-1

我最近不得不这样做,我不喜欢这些解决方案让类类型在当前命名空间中徘徊而没有任何目的的方式。如果您真的只是希望此功能可用于单个类,那么我将使用与上述不同的模式。

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 实践,因为它更好地封装了杂乱的部分,并且不会将任何东西传递到安全继承链中。您也不需要为这种技术修改 Safety 类,使其更加模块化。这可能是许多较新的语言允许嵌套类但几乎没有其他任何东西都借用了朋友概念的原因,即使这只是添加了仅对单个类可用的功能(并且如果 Safety 标记为 final 或标记为重要则不起作用它的部分代码是私有的)。

于 2021-03-27T01:43:41.277 回答