【问题标题】:Allowing a "friend" class to access only some private members允许“朋友”类只访问一些私人成员
【发布时间】:2013-04-09 22:54:09
【问题描述】:

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

【问题讨论】:

  • 不,据我所知没有这样的事情。

标签: c++ friend


【解决方案1】:

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

Is this key-oriented access-protection pattern a known idiom?

【讨论】:

  • 谢谢!我在找它,但不记得名字了:D
  • “没有什么可以让一个类成为一个特定函数的朋友”听起来是错误的方式,顺便说一句。 friend void foo(); 工作得很好,但我认为你的意思是没有办法直接授予对你的一个函数的访问权限。
  • @Xeo 我正确理解了答案中的句子。如果没有friend void foo(),那么句子应该是“没有什么可以让一个特定的函数成为一个类的朋友”。所以对我来说,答案是可以的。
  • 我猜 Passkey-pattern 是完美的解决方案,除了一件事:它有点用不相关的参数污染了界面。
【解决方案2】:

我想你可以在这里使用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*/
    }
};

【讨论】:

  • 这个例子太棒了!
【解决方案3】:

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

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

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

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

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

【讨论】:

  • 如果“一个类”是 C++ 中唯一的自然封装单元,这将是正确的。由于不是,因此此答案包含许多不准确之处。至少,friend functions 不是钝器,它们的存在不是为了解决设计缺陷,而且类确实有业务了解它们。所以至多你的批评应该限于友元类,尽管事实上也有自然(非hacky,无缺陷)封装单元是一组友元类的情况。跨度>
  • “如果您担心您可能会不小心从某个您不打算使用的地方使用 FooA 接口,那么请不要那样做;” ——我最近主要使用 Python,对此我深表同情。通过将所有内容公开,然后 RTFM 不调用未发布的函数,可以节省大量担心访问控制的时间和精力。但这是一种风格,想要使用访问控制的人发现他们必须很好地使用它才能从中受益。
  • @SteveJessop 虽然我同意friend 函数和friend 类都不是总是钝器,但它们在大多数情况下都是这样使用的。而且OP的类星座和“restrictedfriend”的要求明显有味道。
  • @SteveJessop: This would be true if "one class" were the only natural unit of encapsulation in C++. Since it isn't ...我建议是。
  • @LightnessRacesinOrbit:我认为你错了。例如,容器完全没有必要发布其迭代器使用的接口。我也喜欢friend free 函数运算符重载,但我想你会争辩说它们都应该调用已发布的成员函数,例如addincrementprintToStream。我不能说这是错误,我只是认为提供两次界面非常乏味;-)
【解决方案4】:

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

您可以通过 2 种方式更具体地说明您公开的内容:

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

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

.

 friend void FooB::fun();

【讨论】:

    【解决方案5】:

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

    【讨论】:

    • here学习代理类,真的很有用,谢谢!
    【解决方案6】:

    你需要继承。试试这个:

    // _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 方法,它们是私有的。

    【讨论】:

      【解决方案7】:

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

      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,让事情变得简单。

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

      【讨论】:

      • 哦,这很有启发性。我不敢相信我把你的答案留在尘土中超过 4 年!谢谢,对于迟到的感谢感到抱歉。但我认为这个解决方案需要一些修改,因为在 FooC.fun 内部,FooA_for_FooB 也可以用作访问 FooA.Hello 和 FooA.Hello2 的代理。为了避免这种情况,我认为 Hello 和 Hello2 应该被声明为 FooA_for_FooB 的受保护成员,而 FooB 类应该被声明为 FooA_for_FooB 类的友元。你说什么?
      【解决方案8】:

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

      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 类,使其更加模块化。这可能是许多较新的语言允许嵌套类但几乎没有其他任何东西都借用了朋友概念的原因,即使这只是添加了仅对单个类可用的功能(如果安全性被标记为最终或将其代码的重要部分标记为私有)。

      【讨论】:

        猜你喜欢
        • 2021-12-30
        • 1970-01-01
        • 2013-02-13
        • 2021-08-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-08-03
        • 2022-11-13
        相关资源
        最近更新 更多