Friend member function definition conflicts with i

2019-08-10 22:17发布

问题:

I was reading C++ Primer recently and was trapped by the same code in this question (with a little difference), but my question is different. I know that there are a lot of similar questions, but after searching around, it seems no answer could answer my question.

At the very beginning, the code worked fine when I didn't add the friend member function.

/* First class */
class Screen {
public:
    using pos = std::string::size_type;
    using content_type = char;

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

    // Other inline functions are omited.

private:
    pos width = 0;
    pos height = 0;
    std::string contents;
};

/* Second class */
class Window_mgr {
public:
    using screen_index = std::vector<Screen>::size_type;

private:
    std::vector<Screen> screens{Screen(24, 80, ' ')};  // Use `Screen` ctor
};

Then, I want to add the friend member function clear. I wrote two versions of code according to code near page 281 in the book. The first version uses in-class initializer to initialize the vector data member of Window_mgr, while the second version uses constructor initializer list.

The first version (error) just like what the book wrote:

Making a member function a friend requires careful structuring of our programs to accommodate interdependencies among the declarations and definitions. In this example, we must order our program as follows:

  • 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.
/* Part 1, usually Window_mgr.h */
class Screen;

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

private:
    std::vector<Screen> screens{Screen(24, 80, ' ')};  // Error: No ctor for `Screen`
};

/* Part 2, usually Screen.h */
class Screen {
    friend void Window_mgr::clear(screen_index);

public:
    using pos = std::string::size_type;
    using content_type = char;

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

    // Other inline functions are omited.

private:
    pos width = 0;
    pos height = 0;
    std::string contents;
};

/* Part 3, usually Window_mgr.cpp */
void Window_mgr::clear(screen_index i) {
    Screen &s = screens[i];
    s.contents = std::string(s.height * s.width, ' ');
}

The second version (no error), which inspired by this answer:

/* Part 1, usually Window_mgr.h */
class Screen;

class Window_mgr {
public:
    using screen_index = std::vector<Screen>::size_type;
    void clear(screen_index);
    Window_mgr();  // Add ctor declaration

private:
    std::vector<Screen> screens;  // Remove in-class initializer
};

/* Part 2, usually Screen.h */
class Screen {
    friend void Window_mgr::clear(screen_index);

public:
    using pos = std::string::size_type;
    using content_type = char;

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

    // Other inline functions are omited.

private:
    pos width = 0;
    pos height = 0;
    std::string contents;
};

/* Part 3, usually Window_mgr.cpp */
Window_mgr::Window_mgr() : screens{Screen(24, 80, ' ')} {}  // Add ctor definition
// Because `Screen` is already a complete type, we can use its ctor now

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

Note that I put all 3 parts of code in a single file for convenience, they can also be split into three different files with proper include guards and include header files.

I understand forward declaration and the include guards, so I'm not asking about them, here are my questions.

  1. Is constructor defined outside class Window_mgr the only way to initialize the data member screens if I want to use the class Screen directly (not pointer or reference)?

    I think the in-class initializer cannot be used in the first version because Screen is incomplete type when its ctor is called.

  2. Why an incomplete type (Screen) can be used as a parameter to the vector template?

Possible similar questions:

  • Member function a friend
  • Friend Class or Friend member function - Forward declaration and Header inclusion
  • Why aren't my include guards preventing recursive inclusion and multiple symbol definitions?

I was trying to be clearly but seems a little bit verbose, anyway thanks for reading and helping :)