2

I've been trying some examples in a book (C++ Primer by Stanley Lippman) and I understand that a class can make another class its friend (to access some private members). Now I'm reading about a member function being a friend and I try the example

class Screen
{
public:
    friend void Window_mgr::clear();

    typedef std::string::size_type pos;

    Screen () = default;
    Screen (pos ht, pos wd, char c) : height (ht), width (wd),
                                      contents (ht * wd, c) { }

private:
    void do_display (std::ostream &os) const
    {
        os << contents;
    }

    pos cursor = 0;
    pos height = 0, width = 0;
    pos test_num = 100, test_num2 = 222;;
    std::string contents = "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, ' ');
}

but it produces a compiler error saying

Window_mgr has not been declared

and then I read this:

• First, define the Window_mgr class, which declares, but cannot define, clear. Screen must be declared before clear can use the members of Screen.

• Next, define class Screen, including a friend declaration for clear.

• Finally, define clear, which can now refer to the members in Screen.

I don't understand this part -- can someone explain?

Community
  • 1
  • 1
Kryssel Tillada
  • 180
  • 3
  • 13

3 Answers3

3

You are most of the way there, you just need to ensure that you declare all of your classes and functions before you require that declaration.

The first instruction point says to define the Window_mgr class declaring Window_mgr::clear. In order to use Screen, that class must also be declared before Window_mgr. This looks like:

class Screen; //forward-declare Screen so that Window_mgr knows it exists

class Window_mgr {
public:
    //requires forward declaration of Screen, like the above
    using ScreenIndex = std::vector<Screen>::size_type;
    void clear (ScreenIndex); //declare, but don't define, clear
};

The second point says to define Screen and include a friend declaration for Window_mgr::clear. Because that member function has already been declared above, this is valid:

class Screen
{
public:
    using ScreenIndex = std::vector<Screen>::size_type;
    //remember to friend with the same arguments
    friend void Window_mgr::clear(ScreenIndex);
    //...
};

The final point tells you to now define Window_mgr::clear. We have already declared in in the first point, so we just need to do this:

void Window_mgr::clear(ScreenIndex i)
{
    //...
}
TartanLlama
  • 63,752
  • 13
  • 157
  • 193
3

When the compiler gets to friend void Window_mgr::clear(); it has no idea what Window_mgr is as it has not seen that yet. You need to reorder things around a little to get this to work. first you forward declare Screen and then you have you Window_mgr

class Screen;

class Window_mgr {
public:
    using ScreenIndex = std::vector<Screen>::size_type;
    void clear(ScreenIndex);
    Window_mgr();

private:
    std::vector <Screen> screens;  // don't initialize here as we don't know what a screen actually is yet
    //std::vector <Screen> screens {Screen (24, 80, ' ')}; can't do this as we don't what a Screen is here
};

Then you can have you Screen class

class Screen
{
public:
    friend void Window_mgr::clear(ScreenIndex);

    typedef std::string::size_type pos;

    Screen() = default;
    Screen(pos ht, pos wd, char c) : height(ht), width(wd),
        contents(ht * wd, c) { }

private:
    void do_display(std::ostream &os) const
    {
        os << contents;
    }

    pos cursor = 0;
    pos height = 0, width = 0;
    pos test_num = 100, test_num2 = 222;
    std::string contents = "contents";
};

And then you can have the parts of Window_mgr that use Screen

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

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

You can see it all working in this live example

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • one thing why did you declare a constructor in Window_mgr – Kryssel Tillada Jul 10 '15 at 14:30
  • @KrysselTillada the constructor needed to be declared as you were default creating `screens` in you class declaration and you can't do that as the compiler doesn't know what as `Screen` is at that time. – NathanOliver Jul 10 '15 at 14:56
  • @LightnessRacesinOrbit I don't think `Screen` is even evaluated in `using ScreenIndex = std::vector::size_type;` as it gives to bearing to the result. AFAIK `size_type` is not type dependent. – NathanOliver Jul 10 '15 at 15:02
  • @NathanOliver: As a member it cannot escape being type-dependent, I would have thought... consider specialisations. How can the compiler know what `size_type` and whether it even exists? I suppose any specialisation would have to be in scope by that point and the name is not actually a dependent name... is that really enough? Might post a question about this a bit later on. – Lightness Races in Orbit Jul 10 '15 at 15:41
  • Thanks for your answer! I was trapped by the same code here, but my question is a little bit different. I understand the forward declaration part, my question is that is constructor defined outside class `Window_mgr` the only way to initialize the data member `screens`? (Which means the in class initializer cannot be used here like in your code comment because `Screen` is incomplete type.) Any why an incomplete type can be used as a parameter to the vector template? – Jaege Jan 15 '16 at 16:24
  • @Jaege If you have a question you should ask a question. You can link back to this question in your question for reference. – NathanOliver Jan 15 '16 at 16:33
  • @NathanOliver Oh, ok :) – Jaege Jan 15 '16 at 16:40
  • @NathanOliver Would you help me with [this question](http://stackoverflow.com/q/34824503/5340808)? – Jaege Jan 16 '16 at 08:28
1

One problem is that the signature of your clear methods differ. The one declared in the Screen class takes an argument, and the other does not. Signatures must be identical, or the language actually sees them as different functions.

A second problem is that your implementation conflicts with your first point: "define the Window_mgr class, which declares, but cannot define, clear." Your Window_mgr class both declares and defines clear.

sudo make install
  • 5,629
  • 3
  • 36
  • 48