我一直在阅读c++常见问题,对朋友声明很好奇。我个人从未使用过它,但我对探索这门语言很感兴趣。
使用friend的一个好例子是什么?
读了一些常见问题,我喜欢<< >>操作符重载和添加作为这些类的朋友的想法。然而,我不确定这如何不会破坏封装。什么时候这些异常可以保持在OOP的严格范围内?
我一直在阅读c++常见问题,对朋友声明很好奇。我个人从未使用过它,但我对探索这门语言很感兴趣。
使用friend的一个好例子是什么?
读了一些常见问题,我喜欢<< >>操作符重载和添加作为这些类的朋友的想法。然而,我不确定这如何不会破坏封装。什么时候这些异常可以保持在OOP的严格范围内?
当前回答
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指针,以便能够使用数据成员指针访问派生类的数据字段。
其他回答
我发现了一个使用好友访问的方便地方:私有函数的Unittest。
安德鲁例子的另一个常见版本,可怕的密码对联
parent.addChild(child);
child.setParent(parent);
与其担心这两行是否总是一起执行,并且顺序一致,你可以将方法设为私有,并有一个friend函数来强制一致性:
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);
}
换句话说,您可以保持公共接口更小,并强制在友元函数中跨越类和对象的不变量。
你必须非常小心何时何地使用friend关键字,和你一样,我很少使用它。下面是一些关于使用friend和替代用法的注意事项。
假设你想比较两个对象,看它们是否相等。你可以:
使用访问器方法进行比较(检查每个ivar并确定是否相等)。 或者,您可以通过将所有成员设为public直接访问它们。
第一个选项的问题是,这可能会有很多访问器,这比直接变量访问(稍微)慢,更难读取,而且很麻烦。第二种方法的问题是完全破坏了封装。
如果我们可以定义一个外部函数,它仍然可以访问类的私有成员,那就更好了。我们可以用friend关键字做到这一点:
class Beer {
public:
friend bool equal(Beer a, Beer b);
private:
// ...
};
equal(Beer, Beer)方法现在可以直接访问a和b的私有成员(可能是char *brand, float percentAlcohol等)。这是一个相当做作的例子,您可能会更早地将friend应用于重载的==操作符,但我们会讲到这一点。
有几件事需要注意:
A friend is NOT a member function of the class It is an ordinary function with special access to the private members of the class Don't replace all accessors and mutators with friends (you may as well make everything public!) Friendship isn't reciprocal Friendship isn't transitive Friendship isn't inherited Or, as the C++ FAQ explains: "Just because I grant you friendship access to me doesn't automatically grant your kids access to me, doesn't automatically grant your friends access to me, and doesn't automatically grant me access to you."
我只有在用另一种方法更难的时候才会用朋友。另一个例子是,由于Mat2x2, Mat3x3, Mat4x4, Vec2, Vec3, Vec4等的互操作性,许多向量数学函数经常被创建为朋友。而且做朋友比到处使用访问器要容易得多。正如所指出的,friend在应用于<<(非常方便调试)、>>和==运算符时通常很有用,但也可以用于这样的事情:
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这个词,但偶尔它正是你所需要的。希望这能有所帮助!
编辑:阅读faq有点长,我喜欢<< >>操作符重载和添加作为这些类的朋友的想法,但我不确定这如何不打破封装
它将如何破坏封装?
当允许对数据成员进行不受限制的访问时,就打破了封装。考虑以下类:
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程序员感到不舒服,因为当他们说“面向对象”时,他们实际上指的是“Java”。当他们说“封装”时,他们并不是说“必须保护私有成员不受任意访问”,而是说“在Java类中,唯一能够访问私有成员的函数是类成员”,尽管出于几个原因,这完全是无稽之谈。
首先,如上所述,它限制太大。没有理由不允许朋友方法做同样的事情。
第二,限制不够。考虑第四个类:
class c4 {
public:
int getx();
void setx(int x);
private:
int x;
};
根据上述Java思想,这是完美封装的。 但是,它允许任何人读取和修改x,这有什么意义呢?(提示:事实并非如此)
底线: 封装是关于能够控制哪些函数可以访问私有成员。这与这些函数的定义具体位于何处无关。
在我之前工作过的一家公司里,我们遇到了一个有趣的问题,我们用朋友来产生良好的影响。我在我们的框架部门工作,我们在自定义操作系统上创建了一个基本的引擎级系统。在内部我们有一个类结构:
Game
/ \
TwoPlayer SinglePlayer
所有这些类都是框架的一部分,由我们的团队维护。该公司所制作的游戏便是基于这一源自games子游戏的框架。问题是Game有各种单人和双人玩家需要访问的东西的接口,但我们不想在框架类之外公开。解决方案是将这些接口设为私有,并允许双人和单人玩家通过友谊访问它们。
事实上,这整个问题本可以通过更好地实施我们的系统来解决,但我们被锁定在我们所拥有的东西上。