我一直在阅读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指针,以便能够使用数据成员指针访问派生类的数据字段。

其他回答

在工作中,我们广泛地请朋友来测试代码。这意味着我们可以为主应用程序代码提供适当的封装和信息隐藏。但我们也可以有单独的测试代码,使用朋友来检查内部状态和数据进行测试。

可以说,我不会将friend关键字作为设计的重要组成部分。

典型的例子是重载操作符<<。另一个常见的用法是允许助手或管理类访问您的内部。

下面是我从c++朋友那里听到的一些指导原则。最后一个尤其令人难忘。

你的朋友不是你孩子的朋友。 你孩子的朋友并不是你的朋友。 只有朋友才能碰你的隐私部位。

首先(依我看)不要听那些说朋友没用的人。它是有用的。在许多情况下,您将拥有具有不打算公开可用的数据或功能的对象。对于许多作者可能只是表面上熟悉不同领域的大型代码库尤其如此。

友元说明符也有替代方案,但通常都很麻烦(cppp级别的具体类/掩码类型定义),或者不是万无一错(注释或函数名约定)。

在答案上;

友元说明符允许指定类访问发出友元语句的类内受保护的数据或功能。例如,在下面的代码中,任何人都可以询问孩子的名字,但只有母亲和孩子可以更改名字。

您可以通过考虑一个更复杂的类(如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 );
};

在我之前工作过的一家公司里,我们遇到了一个有趣的问题,我们用朋友来产生良好的影响。我在我们的框架部门工作,我们在自定义操作系统上创建了一个基本的引擎级系统。在内部我们有一个类结构:

         Game
        /    \
 TwoPlayer  SinglePlayer

所有这些类都是框架的一部分,由我们的团队维护。该公司所制作的游戏便是基于这一源自games子游戏的框架。问题是Game有各种单人和双人玩家需要访问的东西的接口,但我们不想在框架类之外公开。解决方案是将这些接口设为私有,并允许双人和单人玩家通过友谊访问它们。

事实上,这整个问题本可以通过更好地实施我们的系统来解决,但我们被锁定在我们所拥有的东西上。

好友对于回调也很有用。可以将回调函数作为静态方法来实现

class MyFoo
{
private:
    static void callback(void * data, void * clientData);
    void localCallback();
    ...
};

回调在内部调用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;
}

这样就更容易隐藏下游不需要看到的东西。