【问题标题】:How to declare two classes such that A has members of B and B marks members of A as friends?如何声明两个类以使 A 具有 B 的成员和 B 将 A 的成员标记为朋友?
【发布时间】:2013-09-19 21:19:21
【问题描述】:

我正在尝试做 C++ Primer 5th Edition 中的练习 7.32。该练习提出以下问题:

定义您自己的ScreenWindow_mgr 版本,其中clearWindow_mgr 的成员和Screen 的朋友。

以下是文中给出的ScreenWindow_mgrclear 的定义。

class Screen
{
  public:
    using pos = std::string::size_type;
    Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) { }
  private:
    pos height = 0, width = 0;
    std::string contents;
};

class Window_mgr
{
  public:
    using ScreenIndex = std::vector<Screen>::size_type;
    void clear(ScreenIndex);
  private:
    std::vector<Screen> screens{Screen(24, 80 ' ')};
};

void Window_mgr::clear(ScreenIndex i)
{
  Screen &s = screens[i];
  s.contents = std::string(s.height * s.width, ' ');
}

现在这两个类,如果先定义 Screen 而不是 Window_mgr 就可以按我的预期工作。现在,练习要求我将 clear 设为 Screen 的朋友并定义 clear。要让clear成为会员,如果我理解正确的话,Window_mgr必须被定义。要定义Window_mgr,必须定义Screen。这对我来说似乎是不可能的。

文字给出了以下提示:

使成员函数成为朋友需要仔细构造我们的程序以适应声明和定义之间的相互依赖关系。在这个例子中,我们必须按如下顺序排列我们的程序:

  • 首先,定义Window_mgr 类,它声明但不定义clearScreen 必须在clear 可以使用Screen 的成员之前声明。

  • 接下来,定义类Screen,包括clear 的朋友声明。

  • 最后,定义clear,现在可以引用Screen中的成员。

我尝试解决这个练习的顺序最终是这样的:

class Screen;

class Window_mgr
{
  public:
    using ScreenIndex = std::vector<Screen>::size_type;
    void clear(ScreenIndex);
  private:
    std::vector<Screen> screens{Screen(24, 80 ' ')};
};

class Screen
{
  friend Window_mgr::clear(Window_mgr::ScreenIndex);
  public:
    using pos = std::string::size_type;
    Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) { }
  private:
    pos height = 0, width = 0;
    std::string contents;
};

void Window_mgr::clear(ScreenIndex i)
{
  Screen &s = screens[i];
  s.contents = std::string(s.height * s.width, ' ');
}

这显然行不通,因为Window_mgr 中的向量需要Screen 是一个完整的类型。这似乎是一个无法解决的问题,除非作者不打算使用他们之前介绍的 ScreenWindow_mgr 类。

有没有其他人从 C++ Primer 中解决了这个练习。如果是这样,怎么做?任何帮助如何做到这一点,或者正如我的直觉告诉我的那样,无法做到?

【问题讨论】:

  • "首先,定义 Window_mgr 类 [...] 接下来,定义 Screen" 听起来 Window_mgr 不应有(非指针非引用)类型的成员Screen(或者,在这种情况下为std::vector&lt;Screen&gt;)。
  • 是的,我也是这么想的,但显然他们对clear 的定义使用了他们在Window_mgr 中定义的向量。这似乎真的是一个经过深思熟虑的练习。
  • 我认为使用std::shared_ptr&lt;Screen&gt; 向量是不可能的,嗯。
  • 即使使用std::shared_ptr&lt;Screen&gt;,作者设计的Window_mgr 类也会使用默认屏幕初始化向量,并且需要定义一个非默认构造函数,不是吗?
  • @Adam 你可以定义自己的默认构造函数,但是你必须自己定义,在Screen的定义之后。

标签: c++ class friend forward-declaration


【解决方案1】:

正如 [class.friend]/5 所说:

当友元声明引用重载名称或运算符时,只有参数类型指定的函数成为友元。 X 类的成员函数可以是 Y 类的朋友。

在您的具体情况下:

#include <iostream>
#include <vector>

struct Screen;

class Window_mgr
{
  public:

    Window_mgr();

    using ScreenIndex = std::vector<Screen>::size_type;
    void clear(ScreenIndex);
  private:
    std::vector<Screen> screens;
};

class Screen
{
  friend void Window_mgr::clear(ScreenIndex);
  public:
    using pos = std::string::size_type;
    Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) { }
  private:
    pos height = 0, width = 0;
    std::string contents;
};


Window_mgr::Window_mgr():
  screens{1, Screen(24, 80, ' ') }
{
}

void Window_mgr::clear(ScreenIndex i)
{
  Screen &s = screens[i];
  s.contents = std::string(s.height * s.width, ' ');
}

int main()
{
  Window_mgr w;
  w.clear(0);
}

请注意,无法解决该练习,因为 Window_mgr 具有 std::vector 的成员变量,其参数是不完整的类型。它适用于大多数编译器(请参阅here 为什么),但标准禁止它。

这个例子演示了如何让一个类的成员函数成为朋友:

#include <iostream>

struct A;

struct B
{ 
  void bar( A& a, int l);
};

struct A
{
  friend void B::bar(A&,int);
  A():k(0){}
  private:
  void foo(int m);
  int k;
};



void A::foo(int m)
{
  std::cout<<"A::foo() changing from "<<k<<" to "<<m<<std::endl;
  k=m;
}

void B::bar( A& a, int l)
{
  std::cout<<"B::bar() changing to "<<l<<std::endl;
  a.foo(l);
}

int main()
{
  A a;
  B b;
  b.bar(a,11);
}

【讨论】:

  • @DyP 离开给 OP 做功课,我添加了另一个示例,这说明了我的意思。它必须被定义。
  • 我知道需要先定义一个类,然后才能将它的成员声明为朋友。在您的第二个示例中,您在将 B 的成员声明为 A 中的朋友之前定义了 B。我遇到的问题是我无法首先定义 B 的等价物,因为它具有 A 的向量。
  • @DyP 我也是,直到我意识到std::vector&lt;Screen&gt; 在内部使用Screen 作为指针和引用decls; 作为一个例子。通过删除向量并将Screen screen; 放在那里来证明这一点。它会立即破裂。
  • @DyP 是的,我不想仅仅依靠它。如果有人告诉我这样做,我会用std::vector&lt;std::shared_ptr&lt;Screen&gt;&gt; 悄悄地进入那个美好的夜晚。至少我确定那不是UB。 (无论这是否是,我都不能声称与标准足够亲密以挥动木槌)。
  • @BЈовић "我认为类型在实例化的那一刻就已经完全定义好了" 对于std::vector的成员函数,是的。它们仅在调用时被隐式实例化,并且当时(在此示例中)类型 Screen 是完整的(这就是它起作用的原因)。但是,对于类模板特化std::vector&lt;Screen&gt; 本身(即类型本身),实例化点是之前 Screen 完成。
猜你喜欢
  • 1970-01-01
  • 2017-03-14
  • 1970-01-01
  • 2014-01-29
  • 2016-02-01
  • 2018-08-23
  • 2015-01-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多