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
能够增强封装性。friend
向成员授予 有选择性的访问权限,就像protected
一样。任何细粒度的控制都比公开访问好。其他语言也定义了有选择性的访问机制,例如考虑C#的internal
。关于使用friend
的大部分负面批评与更紧密的耦合有关,这通常被认为是不好的。然而,在某些情况下,更紧密的耦合恰恰是您想要的,而friend
正是给了您这种权力。 - André Caron在工作中,我们广泛地使用“朋友用于测试代码”的技术。这意味着我们可以为主要的应用程序代码提供适当的封装和信息隐藏。同时,我们可以编写独立的测试代码,使用“朋友”来检查内部状态和数据以进行测试。
可以说,我不会将“朋友关键字”作为设计的基本组成部分。
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();
};
template<template<typename> class P> class C:P<C> {};
中的P<C>
处,指出“需要使用类模板C的模板参数”。你是否遇到过同样的问题或者知道解决方案? - bennedichFlexibleClass
中使用 FlexibleClass
应该隐式地引用其自身类型。 - Yakk - Adam Nevraumonterror: type/value mismatch at argument 1 in template parameter list for 'template<class> class SomePolicy' struct FlexibleClass : private SomePolicy<FlexibleClass> ^ crtp.cpp:17:56: note: expected a type, got 'FlexibleClass'
使用gcc真的可以进行这种模板化的CRTP吗?
(请注意,我在开头添加了using some_type = int;
) - Maitre Bartfriend
的部分也不例外。这里唯一真正的观察是 C++ 仅在编译时保证了封装性。而你并不需要更多的话来表达它。其余的都是废话。因此,总之:FQA 中的这个部分不值得一提。 - Konrad Rudolph经典的例子是重载 operator<<。另一个常见用法是允许帮助类或管理员类访问您的内部。
以下是我听说过的一些 C++ 友元函数指导方针。最后一条尤其令人难忘。
编辑:阅读 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
一样,我们可以通过查看类定义来看到完整的访问权限列表。
那么它究竟如何比封装性更差呢?同样的代码可以访问类的私有成员。而且每个有访问权限的人都在类定义中列出。
friend
并不会破坏封装性。它让一些 Java 程序员感到不舒服,因为当他们说“面向对象编程”时,实际上是指“Java”。“封装”并不意味着“必须保护私有成员免于受到任意访问”,而是“只有类成员能够访问私有成员的 Java 类”,即使这对多个原因来说都是完全无稽之谈。
首先,正如已经显示的那样,这种方法过于限制性。没有理由不允许 friend 方法执行相同操作。
其次,它的限制不够。考虑第四个类:
class c4 {
public:
int getx();
void setx(int x);
private:
int x;
};
根据上述Java思想,这是完全封装的。 然而,它允许任何人读取和修改x。这怎么说得通?(提示:并不能)
底线: 封装是关于控制哪些函数可以访问私有成员。它与这些函数的定义准确位置无关。不是关于函数定义的位置。
安德鲁例子中的另一种常见形式是可怕的代码对
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);
}
addChild
成员函数也设置父节点呢? - NawazsetParent
声明为友元函数,因为你不希望客户端改变父对象,因为这个操作应该在 addChild
/removeChild
函数中进行管理。 - Ylisar