什么时候应该在C ++中使用"朋友"?
我一直在阅读C ++常见问题解答,并对" friend"声明感到好奇。我个人从未使用过它,但是我对探索这种语言很感兴趣。
使用friend
的一个好例子是什么?
再读一遍FAQ,我喜欢<<``>>
运算符重载的想法,并添加为这些类的朋友。但是我不确定这不会破坏封装。这些例外什么时候可以保持在OOP的严格程度之内?
解决方案
回答
在为类实现树算法时,教授给我们的框架代码让树类成为了节点类的朋友。
除了让我们无需设置功能即可访问成员变量外,它实际上并没有任何好处。
回答
首先(IMO)不要听那些说"朋友"没有用的人。它是有益的。在许多情况下,我们将拥有对象,这些对象具有不打算公开提供的数据或者功能。对于大型代码库尤其如此,因为许多代码作者可能只对不同领域有初步的了解。
朋友说明符有ARE替代项,但是它们通常很麻烦(cpp级具体类/屏蔽的typedef)或者不十分可靠(注释或者函数名称约定)。
回答问题;
" friend"说明符允许指定的类访问在创建Friend语句的类中受保护的数据或者功能。例如,在下面的代码中,任何人都可以问孩子一个名字,但是只有母亲和孩子可以更改名字。
我们可以通过考虑一个更复杂的类(例如Window)来进一步介绍这个简单的示例。 Window很可能具有许多功能/数据元素,这些功能/数据元素不应公开访问,但相关类(例如WindowManager)需要它们。
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 ); };
回答
树示例是一个很好的示例:
在几个不同的类中实现一个对象而无需
有继承关系。
也许我们还可能需要它来保护构造函数并强制执行
人们使用"朋友"工厂。
...好吧,坦率地说,你可以没有它。
回答
我们使用私有/受保护/公共权限控制成员和功能的访问权限?
因此,假设这3个级别中的每个级别的概念都很明确,那么应该清楚我们缺少了一些东西...
例如,将成员/函数声明为受保护是非常通用的。我们说的是每个人都无法使用此功能(当然,除了继承的孩子)。但是异常呢?每个安全系统都会让我们拥有某种类型的"白名单",对吗?
因此,friend使我们可以灵活地进行坚如磐石的对象隔离,但允许为我们认为合理的事物创建"漏洞"。
我猜人们说它是不需要的,因为总有一种设计会没有它。我认为这类似于对全局变量的讨论:永远不要使用它们,总有一种不用它们的方法...但是实际上,我们会看到最终以(几乎)最优雅的方式出现的情况。 ..我认为与朋友的情况相同。
It doesn't really do any good, other than let you access a member variable without using a setting function
嗯,这并不是看待它的确切方式。
想法是控制WHO可以访问具有或者不具有设置功能的功能。
回答
典型的例子是重载operator <<。另一个常见用途是允许助手或者管理员类访问内部组件。
这是我听说过的一些有关C ++朋友的准则。最后一个特别令人难忘。
- 朋友不是我们孩子的朋友。
- 我们孩子的朋友不是朋友。
- 只有朋友可以触摸私密部位。
回答
@roo:封装在这里没有中断,因为类本身规定了谁可以访问其私有成员。如果封装可能是由类外部引起的,则封装只会被破坏。如果运算符<<
会宣布我是foo
类的朋友。
"朋友"代替了对"公共"的使用,而不是对"私有"的使用!
实际上,C ++ FAQ已经回答了这个问题。
回答
我们可以遵守最严格和最纯正的OOP原则,并确保任何类的数据成员都没有访问器,因此,所有对象必须是唯一可以了解其数据的对象,唯一的处理方式是通过间接消息,即方法。
但是,即使Chas是一个内部可见性关键字,Java在某些方面也具有其默认的程序包级别可访问性。实际上,C ++通过确切指定其他类以及只有其他类可以看到的类,从而最小化了对类的可见性的折衷,从而更接近于OOP的理想状态。
我并没有真正使用C ++,但是如果Chad的朋友愿意,我会使用它而不是Assembly-global内部修饰符,而我实际上使用了很多。它并没有真正破坏封装,因为.NET中的部署单元是一个程序集。
但是接下来是InternalsVisibleToAttribute(otherAssembly),它的作用类似于交叉装配的好友机制。 Microsoft将其用于可视设计器程序集。
回答
为了进行TDD多次,我在C ++中使用了'friend'关键字。
朋友可以知道关于我的一切吗?
更新:我从Bjarne Stroustrup网站上找到了有关" friend"关键字的有价值的答案。
"Friend" is an explicit mechanism for granting access, just like membership.
回答
To do TDD many times I've used 'friend' keyword in C++. Can a friend know everything about me?
不,这只是友谊的一种方式:
回答
创建Singleton类时,我使用" friend"的一个特定实例。 " 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; }
回答
关于运算符<<和operator >>,没有充分的理由结识这些运算符。的确,它们不应成为成员函数,但也不必成为朋友。
最好的办法是创建公共打印(ostream&)和读取(istream&)函数。然后,根据这些函数编写operator <<和operator >>。这提供了使我们能够将这些功能虚拟化的添加好处,从而提供了虚拟序列化。
回答
当我们构建容器并且想要为该类实现迭代器时,Friend会派上用场。
回答
在工作中,我们广泛使用朋友来测试代码。这意味着我们可以为主要应用程序代码提供适当的封装和信息隐藏。但是我们也可以有单独的测试代码,该代码使用朋友检查内部状态和数据以进行测试。
可以说我不会将friend关键字用作我们设计的必要组成部分。
回答
在我以前工作过的一家公司中,我们遇到了一个有趣的问题,当时我们使用朋友来体面地对待。我在框架部门工作,我们在自定义OS上创建了基本的引擎级系统。在内部,我们有一个类结构:
Game / \ TwoPlayer SinglePlayer
所有这些类都是框架的一部分,并由我们的团队维护。该公司生产的游戏是在此框架的基础上构建的,该框架源于一个Games子公司。问题是Game具有与SinglePlayer和TwoPlayer需要访问的各种事物的接口,但是我们不想在框架类之外公开。解决方案是将这些接口设为私有,并允许TwoPlayer和SinglePlayer通过友谊访问它们。
诚然,可以通过更好地实施我们的系统来解决整个问题,但是我们被束之高阁。
回答
安德鲁例子的另一个常见版本,可怕的代码对
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); }
换句话说,我们可以使公共接口更小,并强制执行跨朋友函数中的类和对象的不变式。
回答
朋友对于回调也很有用。我们可以将回调实现为静态方法
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中定义并成为友好对象。这使编码人员可以对标头的用户隐藏该类的许多复杂性和内部工作。
在标题中:
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; }
隐藏下游不需要的东西变得更加容易。
回答
" friend"关键字具有许多很好的用途。这是我立即可以看到的两种用途:
朋友定义
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 }
私人CRTP基本类别
有时,我们发现策略需要访问派生类的需求:
// 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基础将其this指针强制转换为能够使用数据成员指针访问派生类的数据字段。
回答
简短的答案是:当它实际上改善封装时,请使用friend。提高可读性和可用性(运算符<<和>>是典范示例)也是一个很好的理由。
至于改进封装的示例,专门设计为与其他类的内部一起使用的类(想到的是测试类)是不错的选择。
回答
我仅使用friend-keyword来对受保护的功能进行单元测试。有人会说我们不应该测试受保护的功能。但是,我在添加新功能时发现此工具非常有用。
但是,我没有在类声明中直接使用关键字,而是使用了一个漂亮的模板技巧来实现这一点:
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。
回答
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
它将如何破坏封装?
当我们允许对数据成员的无限制访问时,将破坏封装。考虑以下类别:
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
一样,通过查看类定义,我们可以看到谁有权访问的完整列表。
那么,这种封装方式到底有多少呢?相同数量的代码可以访问该类的私有成员。类定义中列出了有权访问的每个人。
"朋友"不会破坏封装。这使某些Java程序员感到不舒服,因为当他们说" OOP"时,实际上是指" Java"。当他们说"封装"时,它们的意思不是"必须保护私有成员免受任意访问",而是"一个Java类,其中唯一能够访问私有成员的功能是类成员",即使对于几个原因。
首先,正如已经显示的那样,它过于严格。没有理由不应该允许朋友方法做同样的事情。
第二,它不够严格。考虑第四类:
class c4 { public: int getx(); void setx(int x); private: int x; };
根据上述Java思维,这是完美封装的。
但是,它绝对允许任何人读取和修改x。那怎么办呢? (提示:没有)
底线:
封装是关于能够控制哪些功能可以访问私有成员。这些功能的定义并不精确地位于何处。
回答
另一个用途:朋友(+虚拟继承)可用于避免从类派生(又名:"使类可被接受")=> 1,2
从2:
class Fred; class FredBase { private: friend class Fred; FredBase() { } }; class Fred : private virtual FredBase { public: ... };
回答
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.
远不止于此...