只允许“好友”类访问某些私人成员

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

提问人:Archer 提问时间:4/17/2013 最后编辑:leemesArcher 更新时间:8/15/2023 访问量:32523

问:

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

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

所以,我想知道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++ 好友

评论

2赞 Kiril Kirov 4/17/2013
不,据我所知,没有这样的事情。

答:

30赞 Steve Jessop 4/17/2013 #1

没有什么可以使类成为某个特定函数的友元,但是您可以使用私有构造函数使“键”类成为友邦,然后将该类作为忽略的参数。 将无法提供参数,因此无法调用:FooBFooA::HelloFooCHello

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

评论

1赞 Matthieu M. 4/17/2013
谢谢!我一直在寻找它,但不记得名字:D
0赞 Xeo 4/17/2013
顺便说一句,“没有什么可以让一个类成为某个特定函数的朋友”听起来是错误的。 工作得很好,但我认为你的意思是没有办法直接授予对你的一个功能的访问权限。friend void foo();
0赞 leemes 4/17/2013
@Xeo我正确理解了答案中的句子。如果没有,则句子应该是“没有什么可以使一个特定函数成为类的朋友”。所以对我来说,答案是正确的。friend void foo()
0赞 Archer 4/17/2013
我想 Passkey-pattern 是完美的解决方案,除了一件事:它有点用不相关的参数污染接口。
34赞 ForEveR 4/17/2013 #2

我认为您可以在这里使用律师-客户

在你的例子中,例子应该是这样的

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*/
    }
};
1赞 Lightness Races in Orbit 4/17/2013 #3

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

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

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

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

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

评论

9赞 Steve Jessop 4/17/2013
如果“一个类”是 C++ 中唯一的自然封装单位,那将是正确的。由于事实并非如此,因此此答案包含许多不准确之处。至少,函数不是钝器,它们的存在不是为了绕过设计缺陷,而且类确实知道它们。因此,你的批评最多应该局限于朋友类,尽管实际上也存在一些情况,即自然的(非黑客的,无缺陷的)封装单元是一组友好的类。friend
1赞 Steve Jessop 4/17/2013
“如果你担心你可能会不小心从你无意的地方使用FooA接口,那么,干脆不要这样做;” - 最近主要使用Python,我确实对此表示同情。通过公开所有内容,然后 RTFM 不调用未发布的函数,可以节省大量担心访问控制的时间和精力。但这是一种风格的东西,想要使用访问控制的人发现他们必须很好地使用它才能从中获得任何好处。
1赞 Arne Mertz 4/17/2013
@SteveJessop虽然我同意函数和类都不是钝器,但在大多数情况下,它们都是这样使用的。而OP的阶级星座和“受限”的要求显然有味道。friendfriendfriend
0赞 Lightness Races in Orbit 4/17/2013
@SteveJessop:......我建议是这样。This would be true if "one class" were the only natural unit of encapsulation in C++. Since it isn't
0赞 Steve Jessop 4/17/2013
@LightnessRacesinOrbit:我建议你错了。例如,容器完全没有必要发布其迭代器使用的接口。我也喜欢朋友自由函数运算符重载,但我想你会争辩说它们都应该调用已发布的成员函数,称为 and 和 之类的东西。我不能说这是错误的,我只是认为提供两次界面是难以言喻的乏味;-)addincrementprintToStream
0赞 Yochai Timmer 4/17/2013 #4

整个想法是将您的班级暴露给朋友。friend

有两种方法可以更具体地说明公开的内容:

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

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

.

 friend void FooB::fun();
1赞 Jerome Baldridge 4/17/2013 #5

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

评论

0赞 Archer 4/20/2013
在这里学习代理类的形式,它真的很有用,谢谢!
2赞 user3737631 6/13/2014 #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
}

这样你就有了:是唯一能够使用的。 无法访问私有方法。ClassBClassAClassB_ClassA

5赞 rolevax 1/27/2016 #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_FooBFooA_for_FooBFooBFooA_for_FooBFooCFooAFooA_for_FooBFooCFooB

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

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

评论

0赞 Archer 6/3/2020
哦,这很有启发性。我简直不敢相信我把你的答案丢在了尘埃里超过 4 年!谢谢,很抱歉迟到了。但我认为这个解决方案需要一些修改,因为在 FooC.fun 内部,FooA_for_FooB 也可以用作访问 FooA.Hello 和 FooA.Hello2 的代理。为了避免这种情况,我认为 Hello 和 Hello2 应该被声明为 FooA_for_FooB 的受保护成员,而 FooB 类应该被声明为 FooA_for_FooB 类的朋友。你说什么?
-1赞 krowe 3/27/2021 #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);
   }
};

然后,在主(或其他任何地方)中,您不能通过 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 被标记为最终或将其代码的重要部分标记为私有,则不起作用)。

2赞 Mashpoe 11/13/2022 #9

您可以在基类中隐藏私有成员,然后使 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 中的私有构造函数以及一些我认为会有所帮助的其他评论。