我一直在阅读c++常见问题,对朋友声明很好奇。我个人从未使用过它,但我对探索这门语言很感兴趣。
使用friend的一个好例子是什么?
读了一些常见问题,我喜欢<< >>操作符重载和添加作为这些类的朋友的想法。然而,我不确定这如何不会破坏封装。什么时候这些异常可以保持在OOP的严格范围内?
我一直在阅读c++常见问题,对朋友声明很好奇。我个人从未使用过它,但我对探索这门语言很感兴趣。
使用friend的一个好例子是什么?
读了一些常见问题,我喜欢<< >>操作符重载和添加作为这些类的朋友的想法。然而,我不确定这如何不会破坏封装。什么时候这些异常可以保持在OOP的严格范围内?
当前回答
另一种用法:friend(+虚拟继承)可以用来避免从一个类派生(又名:“make a class underivable”)=> 1,2
从2:
class Fred;
class FredBase {
private:
friend class Fred;
FredBase() { }
};
class Fred : private virtual FredBase {
public:
...
};
其他回答
您可以坚持最严格和最纯粹的OOP原则,并确保任何类的数据成员都没有访问器,这样所有对象都必须是唯一可以知道它们的数据的对象,并且对它们进行操作的唯一方法是通过间接消息(即方法)。
但即使是c#也有一个内部可见性关键字,Java也有默认的包级可访问性。c++实际上更接近于OOP的理想,它通过精确地指定哪些其他类或只有其他类可以看到一个类,从而最大限度地减少了类的可见性。
我不太使用c++,但如果c#有朋友,我会用它来代替我经常使用的汇编全局内部修饰符。它并没有真正打破封装,因为。net中的部署单元是一个程序集。
但是还有InternalsVisibleToAttribute(otherAssembly),它的作用类似于跨组装的友元机制。微软将此用于可视化设计器程序集。
我使用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;
}
在工作中,我们广泛地请朋友来测试代码。这意味着我们可以为主应用程序代码提供适当的封装和信息隐藏。但我们也可以有单独的测试代码,使用朋友来检查内部状态和数据进行测试。
可以说,我不会将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指针,以便能够使用数据成员指针访问派生类的数据字段。
安德鲁例子的另一个常见版本,可怕的密码对联
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);
}
换句话说,您可以保持公共接口更小,并强制在友元函数中跨越类和对象的不变量。