提问人:Archer 提问时间:4/17/2013 最后编辑:leemesArcher 更新时间:8/15/2023 访问量:32523
只允许“好友”类访问某些私人成员
Allowing a "friend" class to access only some private members
问:
假设我有三个 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
}
};
答:
没有什么可以使类成为某个特定函数的友元,但是您可以使用私有构造函数使“键”类成为友邦,然后将该类作为忽略的参数。 将无法提供参数,因此无法调用:FooB
FooA::Hello
FooC
Hello
评论
friend void foo();
friend void foo()
我认为您可以在这里使用律师-客户。
在你的例子中,例子应该是这样的
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*/
}
};
不,这并不是真正的限制。在我看来,局限性在于——一种绕过设计缺陷的钝器——首先存在。friend
你的班级 FooA
不知道 FooB 和 FooC
以及“哪一个应该能够使用它”。它应该有一个公共接口,而不关心谁可以使用它。这就是界面的重点!在该接口中调用函数应始终保持良好、安全、愉快、一致的状态。
FooA
如果你担心你可能会不小心从你不想去的地方使用界面,那么,干脆不要这样做;C++ 不是一种适合防止此类用户错误的语言。在这种情况下,您的测试覆盖率应该足够了。FooA
严格来说,我相信你可以通过一些可怕的复杂“设计模式”来获得你所追求的功能,但老实说,我不会打扰。
如果这是你的程序设计的语义问题,那么我礼貌地建议你的设计有一个缺陷。
评论
friend
friend
friend
friend
This would be true if "one class" were the only natural unit of encapsulation in C++. Since it isn't
add
increment
printToStream
整个想法是将您的班级暴露给朋友。friend
有两种方法可以更具体地说明公开的内容:
继承自 ,这样只公开受保护的方法和公共方法。
FooA
只与某个方法成为朋友,这样只有该方法才能访问:
.
friend void FooB::fun();
最安全的解决方案是使用另一个类作为两个类的“中间人”,而不是让其中一个类成为 @ForEveR 在答案中建议的一种方法,但您也可以对代理类和其他可以应用的设计模式进行一些搜索。friend.
评论
您将需要继承。试试这个:
// _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
通过从接口类继承类,可以将类的接口部分公开给指定的客户端。
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
FooA_for_FooB
FooC
FooB
这种方法不需要,使事情变得简单。friend
类似的操作可以通过在基类中将所有内容设为私有,并在派生类中有选择地将某些成员包装并公开为公共成员来完成。不过,这种方法有时可能需要丑陋的沮丧。(因为基类将成为整个程序中的“货币”。
评论
我最近不得不这样做,我不喜欢这些解决方案让类类型在当前命名空间中晃来晃去的方式,基本上没有目的。如果你真的只想让这个功能可用于单个类,那么我会使用与提到的不同的模式。
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 被标记为最终或将其代码的重要部分标记为私有,则不起作用)。
您可以在基类中隐藏私有成员,然后使 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 中的私有构造函数以及一些我认为会有所帮助的其他评论。
评论