When should you use 'friend' in C++?
我一直在阅读C++的FAQ,并且对EDCOX1的0个声明感到好奇。我个人从未使用过它,但是我对探索语言感兴趣。
使用
阅读FAQ的时间要长一点,我喜欢
首先(imo)不要听那些说
友元说明符还有其他的选择,但它们通常很麻烦(cpp级的具体类/屏蔽的typedef),或者不是万无一失的(注释或函数名约定)。
找到答案;
通过考虑更复杂的类(如窗口),您可以进一步使用这个简单的示例。很可能一个窗口会有许多不应该公开访问的函数/数据元素,但相关类(如WindowManager)需要这些元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 ); }; |
在工作中,我们广泛使用朋友来测试代码。这意味着我们可以为主要应用程序代码提供适当的封装和信息隐藏。但是我们也可以有单独的测试代码,它使用朋友来检查内部状态和测试数据。
我可以说我不会把friend关键字作为你设计的一个基本组成部分。
友元定义允许在类作用域中定义函数,但该函数不会被定义为成员函数,而是作为封闭命名空间的自由函数,并且除了依赖于参数的查找之外,通常不可见。这使得它对于运算符重载特别有用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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 } |
私有CRTP基类
有时,您会发现策略需要访问派生类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // 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(); }; |
你会在这个答案中找到一个非人为的例子。此答案中使用的另一个代码。crtp基转换它的这个指针,以便能够使用数据成员指针访问派生类的数据字段。
@Roo:封装在这里不会被破坏,因为类本身决定了谁可以访问它的私有成员。只有当这可能是由类外部引起时,封装才会被破坏,例如,如果您的
实际上,C++ FAQ已经回答了这个问题。
典型的例子是重载操作符<。另一个常见的用途是允许助手或管理类访问您的内部。
以下是我听到的关于C++朋友的一些指导原则。最后一个特别值得纪念。
- 你的朋友不是你孩子的朋友。
- 你孩子的朋友不是你的朋友。
- 只有朋友才能接触到你的私处。
edit: Reading the faq a bit longer I like the idea of the << >> operator overloading and adding as a friend of those classes, however I am not sure how this doesn't break encapsulation
它如何打破封装?
当您允许对数据成员进行不受限制的访问时,就会破坏封装。考虑以下类别:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class c1 { public: int x; }; class c2 { public: int foo(); private: int x; }; class c3 { friend int foo(); private: int x; }; |
EDOCX1?5?是不是没那么密封?是否允许不受限制地访问
不,它只允许一个函数访问类的私有成员。就像江户十一〔二〕一样。和
那么,这到底是如何被压缩的呢?相同数量的代码可以访问类的私有成员。所有有权访问的人都列在类定义中。
首先,正如已经显示的,它太过限制了。没有理由不允许朋友方法也这么做。
第二,限制不够。考虑第四类:
1 2 3 4 5 6 7 | class c4 { public: int getx(); void setx(int x); private: int x; }; |
根据上述Java思想,这是完全封装的。然而,它绝对允许任何人读取和修改X,这有什么意义呢?(提示:没有)
底线:封装是指能够控制哪些函数可以访问私有成员。这与这些函数的定义所处的位置无关。
安德鲁例子的另一个常见版本,可怕的代码对联
1 2 | parent.addChild(child); child.setParent(parent); |
如果这两行总是按一致的顺序一起执行,那么您可以将这些方法设置为私有的,并具有一个友元函数来强制实现一致性,而不是担心这两行是否总是按一致的顺序执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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); } |
换句话说,您可以使公共接口更小,并强制执行跨越友元函数中的类和对象的不变量。
您是否使用私有/受保护/公共权限控制成员和函数的访问权限?所以假设这三个层次中的每一个的概念都是明确的,那么很明显我们遗漏了一些东西……
例如,受保护的成员/函数的声明是非常通用的。您的意思是,每个人都无法接触到这个功能(当然,除了继承的孩子)。但是例外呢?每个安全系统都允许你有某种类型的"白名单",对吗?
因此,朋友让你有了岩石固体物体隔离的灵活性,但允许为你认为合理的东西创造一个"漏洞"。
我想人们说这是不需要的,因为总有一个设计会没有它。我认为这类似于对全局变量的讨论:你永远不应该使用它们,没有它们总是有办法做到的…但在现实中,你会看到最终成为(几乎)最优雅的方式的情况…我认为朋友也是这样。
It doesn't really do any good, other than let you access a member variable without using a setting function
好吧,这并不是正确看待它的方式。其目的是控制谁可以访问什么,是否有设置功能与此无关。
我找到了一个方便的地方来使用friend access:UnitTest的私有函数。
当您构建一个容器并且您想要为该类实现一个迭代器时,friend非常有用。
C++的创建者说,这并不妨碍任何封装原理,我将引用他:
Does"friend" violate encapsulation?
No. It does not."Friend" is an explicit mechanism for granting access, just like membership. You cannot (in a standard conforming program) grant yourself access to a class without modifying its source.
很清楚…
简短的答案是:当它确实改进了封装时,使用friend。提高可读性和可用性(操作员<<和>>是典型示例)也是一个很好的原因。
至于改进封装的例子,专门设计用于处理其他类内部的类(请记住测试类)是很好的候选者。
要做TDD很多次,我在C++中使用了"朋友"关键字。
一个朋友能了解我的一切吗?
更新:我从bjarne stroustrup网站找到了这个关于"friend"关键字的有价值的答案。
"Friend" is an explicit mechanism for granting access, just like membership.
在我以前工作过的一家公司里,我们遇到了一个有趣的问题,那就是我们用朋友来影响他人。我在我们的框架部门工作,我们在定制操作系统上创建了一个基本的引擎级系统。在内部,我们有一个类结构:
1 2 3 | Game / \ TwoPlayer SinglePlayer |
所有这些类都是框架的一部分,由我们的团队维护。该公司生产的游戏是建立在这个框架之上的,这个框架源自一个游戏的孩子。问题是,游戏有各种各样的接口,单人玩家和双人玩家需要访问这些接口,但我们不想在框架类之外公开这些接口。解决方案是使这些接口私有化,并允许两个人和单人通过友谊访问它们。
事实上,整个问题可以通过更好地实施我们的系统来解决,但是我们被锁在了我们现有的系统中。
另一个用途:friend(+virtual inheritance)可用于避免从类派生(aka:"make a class underivable")=>1,2
2:
1 2 3 4 5 6 7 8 9 10 11 12 | class Fred; class FredBase { private: friend class Fred; FredBase() { } }; class Fred : private virtual FredBase { public: ... }; |
你必须非常小心使用
假设您想比较两个对象,看看它们是否相等。你可以:
- 使用访问器方法进行比较(检查每个ivar并确定相等)。
- 或者,您可以通过公开这些成员来直接访问它们。
第一个选项的问题是,它可能有很多访问器,这些访问器比直接变量访问慢(略慢),更难读取,而且很麻烦。第二种方法的问题是您完全破坏了封装。
如果我们可以定义一个外部函数,它仍然可以访问类的私有成员,那就更好了。我们可以使用
1 2 3 4 5 6 | class Beer { public: friend bool equal(Beer a, Beer b); private: // ... }; |
方法
需要注意的几点:
friend 不是类的成员函数- 它是一个普通的功能,对该类的私有成员具有特殊的访问权限
- 不要用朋友替换所有的访问器和转换器(你也可以把所有的东西都做成
public ) - 友谊不是互惠的。
- 友谊是不可传递的
- 友谊不是遗传的
- 或者,正如C++ FAQ解释的那样:"仅仅因为我同意你的友谊,我不能自动让你的孩子接近我,不会自动让你的朋友接近我,也不会自动让我接近你。"
我只在很难用另一种方法时才真正使用
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Birds { public: friend Birds operator +(Birds, Birds); private: int numberInFlock; }; Birds operator +(Birds b1, Birds b2) { Birds temp; temp.numberInFlock = b1.numberInFlock + b2.numberInFlock; return temp; } |
正如我所说,我根本不经常使用
我只使用friend关键字来单元测试受保护的函数。有些人会说您不应该测试受保护的功能。但是,在添加新功能时,我发现这个非常有用的工具。
但是,我不会在类声明中直接使用关键字,而是使用漂亮的模板黑客来实现这一点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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: <wyn>friendMe(this, someprotectedobject).someProtectedMethod();</wyn> */ template<typename Tester, typename ParentClass> Friender<Tester, ParentClass> & friendMe(Tester * me, ParentClass & instance) { return (Friender<Tester, ParentClass> &)(instance); } |
这使我能够执行以下操作:
1 | friendMe(this, someClassInstance).someProtectedFunction(); |
至少在GCC和MSVC上工作。
关于operator<<和operator>>,没有充分的理由让这些operator成为朋友。的确,它们不应该是成员函数,但也不需要成为朋友。
最好的做法是创建公共打印(ostream&;)和读取(istream&;)函数。然后,根据这些函数编写操作符<<和operator>>。这使得您可以使这些函数成为虚拟的,这提供了虚拟序列化。
树示例是一个很好的示例:在几个不同的类中实现一个对象有继承关系。
也许您还需要它来保护和强制一个构造函数人们使用你的"朋友"工厂。
…好吧,坦白说,没有它你也能活下去。
在C++中,"朋友"关键字在运算符重载和生成桥方面是有用的。1.)运算符重载中的friend关键字:
例如,运算符重载是:假设我们有一个类"point",它有两个浮点变量
"x"(用于x坐标)和"y"(用于y坐标)。现在我们必须重载
1 2 | 1.Overload"operator <<()" function in"ostream" class. 2.Overload"operator<<()" function in"Point" class. |
。现在第一个选项是不好的,因为如果我们需要为一些不同的类再次重载这个操作符,那么我们必须再次在"ostream"类中进行更改。
这就是为什么第二个是最好的选择。现在编译器可以调用
1 | 1.Using ostream object cout.As: cout.operator<<(Pointobj) (form ostream class).<br/> 2.Call without an object.As: operator<<(cout, Pointobj) (from Point class). |
因为我们已经在点类中实现了重载。因此,要在没有对象的情况下调用此函数,我们必须添加cx1(3)关键字,因为我们可以在没有对象的情况下调用友元函数。现在函数声明为:
BR/>2.)制作桥接时的friend关键字:
假设我们必须建立一个函数,在这个函数中我们必须访问两个或更多类(通常称为"桥")的私有成员。如何执行此操作:
要访问类的私有成员,它应该是该类的成员。现在要访问其他类的私有成员,每个类都应该将该函数声明为友元函数。例如:假设有两个类A和B,一个函数
我认为这有助于理解friend关键字。
To do TDD many times I've used 'friend' keyword in C++.
Can a friend know everything about me?
不,这只是单向友谊:`(
正如朋友声明的参考文件所说:
The friend declaration appears in a class body and grants a function or another class access to private and protected members of the class where the friend declaration appears.
因此,正如提醒我们的那样,有些答案存在技术错误,即
我使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | ///////////////////////// // 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; } |
友元函数和类提供对类的私有成员和受保护成员的直接访问,以避免在一般情况下破坏封装。大多数使用的是Ostream:我们希望能够键入:
1 2 | Point p; cout << p; |
但是,这可能需要访问点的私有数据,因此我们定义重载运算符
1 | friend ostream& operator<<(ostream& output, const Point& p); |
然而,有明显的封装含义。首先,现在friend类或函数可以完全访问该类的所有成员,甚至那些不属于其需要的成员。第二,类和友元的实现现在交织在一起,使得类中的内部更改可以破坏友元。
如果您将朋友视为类的扩展,那么从逻辑上讲,这不是问题。但是,在这种情况下,为什么首先必须刺杀那个朋友。
要实现"朋友"想要实现的目标,但不破坏封装,可以这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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的朋友时的结果相同。编译器将优化掉函数调用,因此这将产生与直接访问相同的指令。
我认为使用"朋友"只是一种捷径,可以说是有好处的,但一定要付出代价。
当不同的类(不是继承自另一个类)使用另一个类的私有成员或受保护成员时,可以使用友谊。
Typical use cases of friend functions are operations that are
conducted between two different classes accessing private or protected
members of both.
来自http://www.cplusplus.com/doc/tutorial/inheritance/。
您可以看到这个例子,其中非成员方法访问类的私有成员。这个方法必须在这个类中声明为该类的朋友。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | // 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() << ' '; return 0; } |
也许我从上面的答案中遗漏了一些东西,但是封装中的另一个重要概念是隐藏实现。减少对私有数据成员(类的实现细节)的访问可以使以后更容易地修改代码。如果朋友直接访问私有数据,对实现数据字段(私有数据)的任何更改都会破坏访问该数据的代码。使用访问方法基本上消除了这一点。我想相当重要。
在为类实现树算法时,教授给我们的框架代码将树类作为节点类的朋友。
除了允许您在不使用设置函数的情况下访问成员变量之外,它实际上没有任何好处。
这可能不是实际的用例情况,但可能有助于说明在类之间使用friend。
会所
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 |
成员类的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | 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 ); } }; |
便利设施
1 | class Amenity{}; |
如果你在这里观察这些类的关系,俱乐部拥有各种不同类型的会员资格和会员访问权。这些成员都是从一个超级类或基类派生的,因为它们都共享一个ID和一个枚举类型,公共类和外部类都可以通过在基类中找到的访问函数访问它们的ID和类型。
但是,通过成员及其派生类的这种层次结构及其与俱乐部会所类的关系,派生类中唯一具有"特殊特权"的是vipmember类。基类和其他2个派生类无法访问俱乐部会所的joinVipEvent()方法,但是VIP成员类具有该特权,就好像它完全可以访问该事件一样。
因此,对于VIP会员和俱乐部来说,这是一条双向通道,其他会员类别受到限制。
朋友对回调也很有用。可以将回调实现为静态方法
1 2 3 4 5 6 7 | class MyFoo { private: static void callback(void * data, void * clientData); void localCallback(); ... }; |
其中
或者…
1 2 3 4 5 | class MyFoo { friend void callback(void * data, void * callData); void localCallback(); } |
这允许friend纯粹在cpp中被定义为C样式的函数,而不是使类混乱。
类似地,我经常看到的一个模式是将一个类的所有真正私有的成员放入另一个类中,这个类在头中声明,在cpp中定义,并且是友好的。这使得编码人员可以对头部的用户隐藏类的大量复杂性和内部工作。
在标题中:
1 2 3 4 5 6 7 8 9 10 11 | class MyFooPrivate; class MyFoo { friend class MyFooPrivate; public: MyFoo(); // Public stuff private: MyFooPrivate _private; // Other private members as needed }; |
在CPP中,
1 2 3 4 5 6 7 8 9 10 11 | class MyFooPrivate { public: MyFoo *owner; // Your complexity here }; MyFoo::MyFoo() { this->_private->owner = this; } |
更容易隐藏下游不需要这样看的东西。
您可以遵循最严格和最纯粹的OOP原则,并确保任何类的数据成员都不具有访问器,这样所有对象都必须是唯一能够了解其数据的对象,对它们采取行动的唯一方法就是通过间接消息,即方法。
但是,即使C语言有一个内部可见性关键字,Java对于某些事物也有默认的包级可访问性。C++通过更精确地指定一个类和其他类可以看到的类的妥协来最小化OOP理想。
我并不真正使用C++,但是如果C语言有朋友,我会用它来代替汇编全局全局修饰符,这实际上是我经常用到的。它并没有真正打破封装,因为.NET中的部署单元是一个程序集。
但是还有一个InternalsVisibleToAttribute(OtherAssembly),它就像一个跨程序集友元机制。Microsoft将其用于可视化设计器程序集。