我一直在阅读C++ FAQ并对friend
声明感到好奇。我个人从未使用它,但我有兴趣探索这种语言。
使用friend
的好例子是什么?
阅读常见问题解答时,我更喜欢<<
>>
运算符重载并添加为这些类的朋友的想法。但是我不确定这是如何不破坏封装的。这些例外什么时候可以保持在OOP的严格范围内?
答案 0 :(得分:316)
首先(IMO)不要听那些说friend
没用的人。它是有益的。在许多情况下,您将拥有数据或功能不可公开的对象。对于具有许多作者的大型代码库尤其如此,这些作者可能只是表面上熟悉不同的领域。
朋友说明符有其他选择,但通常它们很麻烦(cpp级别的具体类/掩码类型的定义)或者不是万无一失的(注释或函数名称约定)。
在答案上;
friend
说明符允许指定的类访问构成friend语句的类中的受保护数据或功能。例如,在下面的代码中,任何人都可以询问孩子的姓名,但只有母亲和孩子可以更改姓名。
您可以通过考虑更复杂的类(如Window)来进一步采用这个简单的示例。很可能一个Window将有许多不应公开访问的函数/数据元素,但是相关类(如WindowManager)需要ARE。
class Child
{
//Mother class members can access the private parts of class Child.
friend class Mother;
public:
string name( void );
protected:
void setName( string newName );
};
答案 1 :(得分:148)
在工作中,我们广泛使用朋友来测试代码。这意味着我们可以为主应用程序代码提供适当的封装和信息隐藏。但我们也可以使用单独的测试代码来检查内部状态和数据以进行测试。
我只想说我不会将friend关键字用作您设计的重要组成部分。
答案 2 :(得分:88)
friend
关键字有很多好用途。以下是我可以立即看到的两种用法:
朋友定义允许在类范围内定义一个函数,但该函数不会被定义为成员函数,而是作为封闭命名空间的自由函数,并且除了参数依赖查找之外不会正常显示。这使得它对运算符重载特别有用:
namespace utils {
class f {
private:
typedef int int_type;
int_type value;
public:
// let's assume it doesn't only need .value, but some
// internal stuff.
friend f operator+(f const& a, f const& b) {
// name resolution finds names in class-scope.
// int_type is visible here.
return f(a.value + b.value);
}
int getValue() const { return value; }
};
}
int main() {
utils::f a, b;
std::cout << (a + b).getValue(); // valid
}
有时,您发现策略需要访问派生类:
// possible policy used for flexible-class.
template<typename Derived>
struct Policy {
void doSomething() {
// casting this to Derived* requires us to see that we are a
// base-class of Derived.
some_type const& t = static_cast<Derived*>(this)->getSomething();
}
};
// note, derived privately
template<template<typename> class SomePolicy>
struct FlexibleClass : private SomePolicy<FlexibleClass> {
// we derive privately, so the base-class wouldn't notice that,
// (even though it's the base itself!), so we need a friend declaration
// to make the base a friend of us.
friend class SomePolicy<FlexibleClass>;
void doStuff() {
// calls doSomething of the policy
this->doSomething();
}
// will return useful information
some_type getSomething();
};
你会在this回答中找到一个非人为的例子。使用它的另一个代码是this回答。 CRTP基类会转换它的this指针,以便能够使用data-member-pointers访问派生类的数据字段。
答案 3 :(得分:40)
@roo:封装不会在这里被破坏,因为类本身决定谁可以访问其私有成员。如果这可能是从课外引起的,例如,封装只会被打破。如果您的operator <<
宣称“我是班级foo
的朋友。”
friend
取代public
的使用,而不是使用private
!
实际上,C ++ FAQ answers this already。
答案 4 :(得分:27)
规范示例是重载运算符&lt;&lt;。另一个常见用途是允许帮助者或管理员类访问您的内部。
以下是我听说过有关C ++朋友的一些指导原则。最后一个特别值得纪念。
答案 5 :(得分:16)
编辑:读取常见问题时间我更喜欢&lt;&lt;&lt;&lt;&lt; &GT;&GT;运算符重载并添加为这些类的朋友,但是我不确定这是如何不破坏封装的
它如何打破封装?
当您允许不受限制地访问数据成员时,您会破坏封装。请考虑以下类:
class c1 {
public:
int x;
};
class c2 {
public:
int foo();
private:
int x;
};
class c3 {
friend int foo();
private:
int x;
};
c1
显然未封装。任何人都可以阅读和修改其中的x
。我们无法强制执行任何类型的访问控制。
c2
显然是封装的。没有x
的公开访问权限。您所能做的就是调用foo
函数,该函数在类上执行一些有意义的操作。
c3
?是不是封装了?是否允许不受限制地访问x
?它是否允许未知功能访问?
没有。它允许精确的一个函数访问类的私有成员。就像c2
一样。就像c2
一样,具有访问权限的一个函数不是“一些随机的,未知的函数”,而是“类定义中列出的函数”。就像c2
一样,我们只需查看类定义就可以看到有权访问的完整列表。
那么这封装的确切程度如何呢?相同数量的代码可以访问该类的私有成员。并且具有访问权限的所有人都列在类定义中。
friend
不破坏封装。它让一些Java程序员感到不舒服,因为当他们说“OOP”时,他们实际上意味着“Java”。当他们说“封装”时,他们并不意味着“必须保护私有成员免受任意访问”,而是“只有能够访问私有成员的Java类,是类成员”,即使这是完全无意义的< em>有几个原因。
首先,如图所示,它太有限了。没有理由不允许朋友方法做同样的事情。
其次,它不是限制足够的。考虑第四类:
class c4 {
public:
int getx();
void setx(int x);
private:
int x;
};
根据上述Java心态,这是完全封装的。 然而,它绝对允许任何人阅读和修改x 。这怎么有意义呢? (提示:它没有)
底线: 封装是关于能够控制哪些功能可以访问私有成员。 不关于这些函数的定义的确切位置。
答案 6 :(得分:10)
Andrew的另一个常见版本的例子,可怕的代码对联
parent.addChild(child);
child.setParent(parent);
如果两条线总是以一致的顺序一起完成而不是担心,你可以将这些方法设为私有,并使用友元函数来强制执行一致性:
class Parent;
class Object {
private:
void setParent(Parent&);
friend void addChild(Parent& parent, Object& child);
};
class Parent : public Object {
private:
void addChild(Object& child);
friend void addChild(Parent& parent, Object& child);
};
void addChild(Parent& parent, Object& child) {
if( &parent == &child ){
wetPants();
}
parent.addChild(child);
child.setParent(parent);
}
换句话说,您可以保持公共接口更小,并强制执行跨越友元函数中的类和对象的不变量。
答案 7 :(得分:8)
您使用私人/受保护/公共权限控制成员和功能的访问权限? 所以假设这3个级别中的每个级别的想法都很清楚,那么应该很清楚我们错过了一些东西......
例如,成员/函数声明受保护是非常通用的。你说这个功能对于每个人是不可及的(当然除了继承的孩子)。但是例外呢?每个安全系统都可以让你拥有某种类型的“白名单”吗?
所以朋友让你可以灵活地拥有坚如磐石的对象隔离,但允许为你觉得合理的东西创造一个“漏洞”。
我猜人们说这不是必需的,因为总有一种设计可以不用它。我认为它类似于对全局变量的讨论:你永远不应该使用它们,总有一种方法可以不用它们......但实际上,你会看到最终成为(几乎)最优雅方式的情况。 ..我认为这与朋友的情况相同。
好吧,这不是看待它的方式。 我们的想法是控制世界卫生组织可以访问设置功能与设置功能无关的内容。除了让你在不使用设置功能
的情况下访问成员变量之外,它没有任何好处
答案 8 :(得分:8)
我找到了方便使用朋友访问的地方:私人功能的单元测试。
答案 9 :(得分:5)
当您构建容器并且想要为该类实现迭代器时,Friend会派上用场。
答案 10 :(得分:4)
简短的回答是:当提高封装时,使用朋友。提高可读性和可用性(运算符&lt;&lt;&gt;&gt;是规范示例)也是一个很好的理由。
至于改进封装的例子,专门设计用于与其他类的内部(测试类一起考虑)的类是很好的候选者。
答案 11 :(得分:4)
C ++的创建者说这并不代表任何封装原则,我会引用他的话:
“朋友”是否违反了封装? 不,不是的。 “朋友”是一种授予访问权限的显式机制,就像成员资格一样。你不能(在标准的符合程序中)授予自己访问类而不修改其来源的权利。
不仅仅是明确......
答案 12 :(得分:3)
另一种用法:朋友(+虚拟继承)可用于避免从类派生(又名:“使类不可判断”)=&gt; 1,2
来自2:
class Fred;
class FredBase {
private:
friend class Fred;
FredBase() { }
};
class Fred : private virtual FredBase {
public:
...
};
答案 13 :(得分:3)
我之前在一家公司工作过的地方出现了一个有趣的问题。我在框架部门工作,我们在自定义操作系统上创建了一个基本的引擎级系统。在内部,我们有一个类结构:
Game
/ \
TwoPlayer SinglePlayer
所有这些类都是框架的一部分,由我们的团队维护。该公司制作的游戏是建立在这个框架之上的,该游戏来自游戏儿童之一。问题在于游戏具有与SinglePlayer和TwoPlayer需要访问的各种事物的接口,但我们不希望在框架类之外暴露。解决方案是将这些接口设为私有,并允许TwoPlayer和SinglePlayer通过友谊访问它们。
说实话,整个问题可以通过更好地实施我们的系统来解决,但我们被锁定在我们拥有的东西中。
答案 14 :(得分:3)
要做多次TDD,我在C ++中使用了'friend'关键字。
朋友可以了解我的一切吗?
更新:我从Bjarne Stroustrup site找到了关于“朋友”关键字的有价值的答案。
“朋友”是一种授予访问权限的显式机制,就像会员资格一样。
答案 15 :(得分:2)
关于运营商&lt;&lt;和运算符&gt;&gt;没有充分的理由让这些运营商成为朋友。确实,他们不应该是会员职能,但他们也不需要成为朋友。
最好的办法是创建公共打印(ostream&amp;)和读取(istream&amp;)函数。然后,编写运算符&lt;&lt;和运算符&gt;&gt;就这些功能而言。这提供了额外的好处,允许您将这些功能虚拟化,从而提供虚拟序列化。
答案 16 :(得分:2)
您必须非常小心使用friend
关键字的时间/地点,并且像您一样,我很少使用它。以下是使用friend
和替代方案的一些注意事项。
假设您要比较两个对象,看它们是否相等。你可以:
第一个选项的问题在于,这可能是很多访问者,它比直接变量访问(稍微)慢,更难以阅读,并且很麻烦。第二种方法的问题是你完全打破了封装。
如果我们能够定义一个仍然可以访问类的私有成员的外部函数,那将是多么好的。我们可以使用friend
关键字:
class Beer {
public:
friend bool equal(Beer a, Beer b);
private:
// ...
};
方法equal(Beer, Beer)
现在可以直接访问a
和b
的私人成员(可能是char *brand
,float percentAlcohol
等。这是一个相当人为的例子,你很快会将friend
应用于重载的== operator
,但我们会做到这一点。
有几点需要注意:
friend
不是班级的成员函数public
!)我真的只使用friends
,而另一方面更难做到这一点。另一个例子是,由于friends
,Mat2x2
,Mat3x3
,Mat4x4
,{{1}的互操作性,许多矢量数学函数通常创建为Vec2
。而且,成为朋友更容易,而不是在任何地方都使用访问器。正如所指出的,Vec3
在应用于Vec4
(非常方便调试),friend
和<<
运算符时通常很有用,但也可以用于某些内容像这样:
>>
正如我所说的,我根本不经常使用==
,但偶尔也只是你所需要的。希望这有帮助!
答案 17 :(得分:2)
我只使用friend-keyword来对受保护的功能进行单元测试。有人会说你不应该测试受保护的功能。但是,在添加新功能时,我发现了这个非常有用的工具。
但是,我不直接在类声明中使用关键字,而是使用一个漂亮的模板 - hack来实现这个:
template<typename T>
class FriendIdentity {
public:
typedef T me;
};
/**
* A class to get access to protected stuff in unittests. Don't use
* directly, use friendMe() instead.
*/
template<class ToFriend, typename ParentClass>
class Friender: public ParentClass
{
public:
Friender() {}
virtual ~Friender() {}
private:
// MSVC != GCC
#ifdef _MSC_VER
friend ToFriend;
#else
friend class FriendIdentity<ToFriend>::me;
#endif
};
/**
* Gives access to protected variables/functions in unittests.
* Usage: <code>friendMe(this, someprotectedobject).someProtectedMethod();</code>
*/
template<typename Tester, typename ParentClass>
Friender<Tester, ParentClass> &
friendMe(Tester * me, ParentClass & instance)
{
return (Friender<Tester, ParentClass> &)(instance);
}
这使我能够执行以下操作:
friendMe(this, someClassInstance).someProtectedFunction();
至少在GCC和MSVC上工作。
答案 18 :(得分:1)
Friend函数和类提供对类的私有成员和受保护成员的直接访问,以避免在一般情况下破坏封装。大多数用法都是使用ostream:我们希望能够输入:
Point p;
cout << p;
但是,这可能需要访问Point的私有数据,因此我们定义了重载的运算符
friend ostream& operator<<(ostream& output, const Point& p);
然而,有明显的封装含义。首先,现在朋友类或函数可以完全访问类的所有成员,甚至是那些与其需求无关的成员。其次,类和朋友的实现现在陷入了这样一种情况,即类中的内部变化可能会破坏朋友。
如果您将朋友视为班级的扩展,那么从逻辑上讲,这不是问题。但是,在这种情况下,为什么有必要首先将朋友说出来。
为了实现'朋友'声称要达到的目标,但没有打破封装,可以做到这一点:
class A
{
public:
void need_your_data(B & myBuddy)
{
myBuddy.take_this_name(name_);
}
private:
string name_;
};
class B
{
public:
void print_buddy_name(A & myBuddy)
{
myBuddy.need_your_data(*this);
}
void take_this_name(const string & name)
{
cout << name;
}
};
封装没有被破坏,B类无法访问A中的内部实现,但结果与我们宣布B是A的朋友一样。 编译器将优化掉函数调用,因此这将产生与直接访问相同的指令。
我认为使用'朋友'只是一种可以带来好处但却有明确成本的捷径。
答案 19 :(得分:1)
在C ++中,“friend”关键字在运算符重载和制作桥时非常有用。
1.)运算符重载中的Friend关键字:
运算符重载的示例是:假设我们有一个类“Point”,它有两个浮点变量
“x”(对于x坐标)和“y” (对于y坐标)。现在我们必须重载"<<"
(提取运算符),这样如果我们调用"cout << pointobj"
那么它将打印x和y坐标(其中pointobj是Point类的对象)。为此,我们有两个选择:
1.Overload "operator <<()" function in "ostream" class. 2.Overload "operator<<()" function in "Point" class.现在第一个选项并不好,因为如果我们需要为一些不同的类再次重载此运算符,那么我们必须再次在“ostream”类中进行更改。
"operator <<()"
功能:
1.Using ostream object cout.As: cout.operator<<(Pointobj) (form ostream class).
2.Call without an object.As: operator<<(cout, Pointobj) (from Point class).
因为我们在Point类中实现了重载。因此,要在没有对象的情况下调用此函数,我们必须添加"friend"
关键字,因为我们可以在没有对象的情况下调用友元函数。
现在功能声明将是As:
"friend ostream &operator<<(ostream &cout, Point &pointobj);"
2.)制作桥梁时的朋友关键词:
假设我们必须创建一个函数,我们必须访问两个或更多类的私有成员(通常称为“桥”)。
怎么做:
要访问类的私有成员,它应该是该类的成员。现在要访问其他类的私有成员,每个类都应该将该函数声明为友元函数。例如 :
假设有两个类A和B.函数"funcBridge()"
想要访问这两个类的私有成员。然后两个班都应该将"funcBridge()"
声明为:
friend return_type funcBridge(A &a_obj, B & b_obj);
我认为这有助于理解朋友关键字。
答案 20 :(得分:1)
正如friend declaration的参考说:
友情声明出现在一个类主体中,并授予一个函数或另一个类访问友元声明出现的类的私有和受保护的成员。
所以提醒一下,某些答案中存在技术错误,表示friend
只能访问受保护的成员。
答案 21 :(得分:1)
我使用friend
的一个特定实例是创建Singleton类时。 friend
关键字允许我创建一个访问器函数,它比在类上始终使用“GetInstance()”方法更简洁。
/////////////////////////
// Header file
class MySingleton
{
private:
// Private c-tor for Singleton pattern
MySingleton() {}
friend MySingleton& GetMySingleton();
}
// Accessor function - less verbose than having a "GetInstance()"
// static function on the class
MySingleton& GetMySingleton();
/////////////////////////
// Implementation file
MySingleton& GetMySingleton()
{
static MySingleton theInstance;
return theInstance;
}
答案 22 :(得分:1)
多次做TDD我在C ++中使用过'friend'关键字。
朋友可以了解我的一切吗?
不,它只是一种单向的友谊:`(
答案 23 :(得分:1)
树示例是一个很好的例子: 让一个对象在几个不同的类中实现而没有 有继承关系。
也许您可能还需要它来保护构造函数并强制执行 人们使用你的“朋友”工厂。
......好吧,坦率地说,你可以没有它。
答案 24 :(得分:0)
当不同的类(不从另一个类继承)使用其他类的私有或受保护成员时,您可以使用友谊。
友元函数的典型用例是操作 访问私人或受保护的两个不同类之间进行 两者的成员。
来自http://www.cplusplus.com/doc/tutorial/inheritance/。
您可以看到此示例,其中非成员方法访问类的私有成员。必须在此类中声明此方法作为类的朋友。
// friend functions
#include <iostream>
using namespace std;
class Rectangle {
int width, height;
public:
Rectangle() {}
Rectangle (int x, int y) : width(x), height(y) {}
int area() {return width * height;}
friend Rectangle duplicate (const Rectangle&);
};
Rectangle duplicate (const Rectangle& param)
{
Rectangle res;
res.width = param.width*2;
res.height = param.height*2;
return res;
}
int main () {
Rectangle foo;
Rectangle bar (2,3);
foo = duplicate (bar);
cout << foo.area() << '\n';
return 0;
}
答案 25 :(得分:0)
可能我错过了上面的答案,但封装中的另一个重要概念是隐藏实现。减少对私有数据成员的访问(类的实现细节)允许以后更容易地修改代码。如果朋友直接访问私有数据,则对实现数据字段(私有数据)的任何更改都会破坏访问该数据的代码。使用访问方法大多消除了这一点我想是相当重要的。
答案 26 :(得分:0)
这可能不是实际的用例情况,但可能有助于说明在类之间使用朋友。
ClubHouse
class ClubHouse {
public:
friend class VIPMember; // VIP Members Have Full Access To Class
private:
unsigned nonMembers_;
unsigned paidMembers_;
unsigned vipMembers;
std::vector<Member> members_;
public:
ClubHouse() : nonMembers_(0), paidMembers_(0), vipMembers(0) {}
addMember( const Member& member ) { // ...code }
void updateMembership( unsigned memberID, Member::MembershipType type ) { // ...code }
Amenity getAmenity( unsigned memberID ) { // ...code }
protected:
void joinVIPEvent( unsigned memberID ) { // ...code }
}; // ClubHouse
会员班
class Member {
public:
enum MemberShipType {
NON_MEMBER_PAID_EVENT, // Single Event Paid (At Door)
PAID_MEMBERSHIP, // Monthly - Yearly Subscription
VIP_MEMBERSHIP, // Highest Possible Membership
}; // MemberShipType
protected:
MemberShipType type_;
unsigned id_;
Amenity amenity_;
public:
Member( unsigned id, MemberShipType type ) : id_(id), type_(type) {}
virtual ~Member(){}
unsigned getId() const { return id_; }
MemberShipType getType() const { return type_; }
virtual void getAmenityFromClubHouse() = 0
};
class NonMember : public Member {
public:
explicit NonMember( unsigned id ) : Member( id, MemberShipType::NON_MEMBER_PAID_EVENT ) {}
void getAmenityFromClubHouse() override {
Amenity = ClubHouse::getAmenity( this->id_ );
}
};
class PaidMember : public Member {
public:
explicit PaidMember( unsigned id ) : Member( id, MemberShipType::PAID_MEMBERSHIP ) {}
void getAmenityFromClubHouse() override {
Amenity = ClubHouse::getAmenity( this->id_ );
}
};
class VIPMember : public Member {
public:
friend class ClubHouse;
public:
explicit VIPMember( unsigned id ) : Member( id, MemberShipType::VIP_MEMBERSHIP ) {}
void getAmenityFromClubHouse() override {
Amenity = ClubHouse::getAmenity( this->id_ );
}
void attendVIPEvent() {
ClubHouse::joinVIPEvent( this->id );
}
};
<强>设施强>
class Amenity{};
如果你看看这些类的关系; ClubHouse拥有各种不同类型的会员资格和会员资格。成员都派生自超类或基类,因为它们共享一个ID和一个常见的枚举类型,外部类可以通过基类中的访问函数访问它们的ID和类型。
然而,通过会员及其衍生课程的这种层次结构以及他们与ClubHouse课程的关系,唯一一个具有特殊权限的派生课程#34;是VIPMember课程。基类和其他2个派生类无法访问ClubHouse的joinVIPEvent()方法,但VIP成员类具有该特权,就好像它具有对该事件的完全访问权限一样。
因此,对于VIP会员和ClubHouse,它是一条双向访问的街道,其他会员类是有限的。
答案 27 :(得分:0)
在为类实现树算法时,教授给我们的框架代码将树类作为节点类的朋友。
除了让你在不使用设置功能的情况下访问成员变量之外,它没有任何好处。
答案 28 :(得分:-1)
朋友对回调也很有用。您可以将回调实现为静态方法
class MyFoo
{
private:
static void callback(void * data, void * clientData);
void localCallback();
...
};
其中callback
在内部调用localCallback
,clientData
包含您的实例。在我看来,
...或
class MyFoo
{
friend void callback(void * data, void * callData);
void localCallback();
}
这允许朋友纯粹在cpp中定义为c风格的函数,而不是使类混乱。
类似地,我经常看到的模式是将类的所有真正私有成员放入另一个类中,该类在标题中声明,在cpp中定义,并且是friended。这允许编码器从标题的用户隐藏很多复杂性和内部工作。
在标题中:
class MyFooPrivate;
class MyFoo
{
friend class MyFooPrivate;
public:
MyFoo();
// Public stuff
private:
MyFooPrivate _private;
// Other private members as needed
};
在cpp中,
class MyFooPrivate
{
public:
MyFoo *owner;
// Your complexity here
};
MyFoo::MyFoo()
{
this->_private->owner = this;
}
隐藏下游不需要这样看的东西变得更容易。
答案 29 :(得分:-1)
您可以遵守最严格和最纯粹的OOP原则,并确保任何类的数据成员都没有访问者,以便所有对象必须是唯一能够了解其数据的人,唯一可以采取行动的方法是通过间接消息,即方法。
但即使是C#也有一个内部可见性关键字,而Java也有一些默认的包级别的可访问性。 C ++实际上更接近OOP理想,通过指定 完全 来最小化对类的可见性的损害,其他类和仅其他类可以看到进去。
我真的没有使用C ++,但如果C#有朋友,那我就不会使用程序集全局内部修饰符,我实际上使用了很多。它并没有真正打破封装,因为.NET 中的部署单元是一个程序集。
但是, InternalsVisibleTo 属性(otherAssembly)就像一个交叉汇编的朋友机制。 Microsoft将此用于可视设计器程序集。