Дружественные функции - Порядок объявления

У меня есть два класса с именами Screen и Window_mgr.

Screen позволяет Window_mgr изменять свои частные/защищенные члены через объявление функции друга.

В результате Window_mgr определяет в самом конце кода не являющуюся членом функцию с именем Window_mgr::clear, которая должна ее использовать.

К сожалению, я получаю некоторые нелепые ошибки, которые я не могу объяснить сам.

Что мне не хватает?

введите здесь описание изображения

Экран.ч

#pragma once

#ifndef SCREEN_H
#define SCREEN_H

#include <string>
#include <vector>

class Window_mgr {
public:
    // location ID for each screen on the window
    using ScreenIndex = std::vector<Screen>::size_type;
    // reset the Screen at the given position to all blanks
    void clear(ScreenIndex);
private:
    std::vector<Screen> screens{ Screen(24, 80, ' ') };
};

class Screen {

public:

    // Friends
    friend void Window_mgr::clear(ScreenIndex);
    //friend class Window_mgr;

    // Fields
    // typedef => creates an alias
    // typedef std::string::size_type pos;
    // alternative way to declare a type member using a type alias
    using pos = std::string::size_type;

    // Constructors
    Screen() = default; // needed because Screen has another constructor
                        // cursor initialized to 0 by its in-class initializer
    Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {} // get the character at the cursor
    Screen &display(std::ostream &os) // function is in the class body => implicitly inline
    {
        do_display(os);
        return *this;
    }
    const Screen &display(std::ostream &os) const // function is in the class body => implicitly inline
    {
        do_display(os);
        return *this;
    }

    // Methods
    char get() const { return contents[cursor]; } // implicitly inline
    inline char get(pos ht, pos wd) const; // explicitly inline
    Screen &move(pos r, pos c); // can be made inline later
    Screen &set(char);
    Screen &set(pos, pos, char);

private:
    // Fields
    mutable size_t access_ctr;
    pos cursor = 0;
    pos height = 0, width = 0;
    std::string contents;

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

inline Screen &Screen::set(char c)
{
    contents[cursor] = c; // set the new value at the current cursor location
    return *this; // return this object as an lvalue
}

inline Screen &Screen::set(pos r, pos col, char ch)
{
    contents[r*width + col] = ch; // set specified location to given value
    return *this; // return this object as an lvalue
}

// we can specify inline on the definition
inline Screen &Screen::move(pos r, pos c) {
    pos row = r * width; // compute the row location
    cursor = row + c; // move cursor to the column within that row
    return *this; // return this object as an lvalue
}

char Screen::get(pos r, pos c) const // declared as inline in the class
{
    pos row = r * width; // compute row location
    return contents[row + c]; // return character at the given column
}

void Window_mgr::clear(ScreenIndex i)
{
    // s is a reference to the Screen we want to clear
    Screen &s = screens[i];
    // reset the contents of that Screen to all blanks
    s.contents = string(s.height * s.width, ' ');
}

#endif

person HansMusterWhatElse    schedule 19.01.2016    source источник
comment
Вам не хватает #include <vector> и #include <iostream>?   -  person Sergey Kalinichenko    schedule 19.01.2016
comment
извините, забыл скопировать его самую последнюю версию, даже с включением вектора я все равно получаю сообщение об ошибке, касающееся экрана: необъявленный идентификатор и так далее.   -  person HansMusterWhatElse    schedule 19.01.2016
comment
Я добавил class Screen; для объявления Screen в начало файла, но это не решает проблему полностью.   -  person HansMusterWhatElse    schedule 19.01.2016
comment
class Screen; вверху решит эту проблему, если вы захотите выполнить vector<Screen*>, но для vector<Screen> должен быть доступен полный Screen.   -  person Sergey Kalinichenko    schedule 19.01.2016


Ответы (2)


Вы не можете объявить вектор объектов Screen внутри вашего класса Window_mgr, потому что Screen не известен компилятору в этой точке вашего кода. Если бы вам нужно было объявить вектор указателей, вы могли бы исправить это, предварительно объявив Screen, но для вектора реальных объектов должно быть доступно полное определение.

Вам нужно поменять порядок Window_mgr и Screen и объявить дружбу Window_mgr классу:

class Screen {
public:
    friend class Window_mgr;
    ...
};
class Window_mgr {
public:
    // location ID for each screen on the window
    using ScreenIndex = std::vector<Screen>::size_type;
    // reset the Screen at the given position to all blanks
    void clear(ScreenIndex);
private:
    std::vector<Screen> screens{ Screen(24, 80, ' ') };
};

почему компилятор знает Window_mgr, но не знает Window_mgr::ScreenIndex

C++ имеет специальное правило для имен классов, используемых в объявлениях дружбы:

Если имя класса, используемого в объявлении друга, еще не объявлено, оно сразу объявляется вперед.

Вот как компилятор "знает" Window_mgr (т. е. не знает, он верит вам на слово). Нет такого правила для функций-членов или типов-членов, объявленных внутри «дружественных» классов. Вот почему компилятор не знает о Window_mgr::ScreenIndex.

person Sergey Kalinichenko    schedule 19.01.2016
comment
большое спасибо за ваш ответ - это было одно из решений, которое я в конце концов нашел. К сожалению, это не работает с friend void Window_mgr::clear(ScreenIndex); вместо friend class Window_mgr; - это потому, что объявление функции друга само по себе ссылается на ScreenIndex из Window_mgr. Есть идеи, как можно избежать этой перекрестной ссылки? - person HansMusterWhatElse; 19.01.2016
comment
да, все та же ошибка. Во всяком случае, я думал, что будет простое решение для этого. Хотя я до сих пор не совсем понимаю, почему проблема не с friend class Window_mgr, а с friend void Window_mgr::clear(Window_mgr::ScreenIndex);. Оба относятся к Window_mgr или к одному из его элементов, которые на данный момент не должны быть известны компилятору. - person HansMusterWhatElse; 19.01.2016
comment
@HansMusterWhatElse, чтобы перенаправить объявление функции, компилятор должен иметь определение типов параметров, в то время, когда вы перенаправляете объявление Window_mgr::clear(Window_mgr::ScreenIndex), компилятор не знает, что такое Window_mgr::ScreenIndex то есть вы откладываете определение Window_mgr до тех пор, пока не будет определен Screen. - person amdn; 19.01.2016
comment
Так почему же компилятор знает Window_mgr, но не знает Window_mgr::ScreenIndex? Основываясь на знании того, что он отлично работает с friend class Window_mgr; - person HansMusterWhatElse; 19.01.2016
comment
@HansMusterWhatElse Пожалуйста, смотрите редактирование для объяснения этой разницы. - person Sergey Kalinichenko; 19.01.2016

Я рекомендую не использовать для этого «друга». Просто добавьте публичную функцию-член clear() в класс Screen и вызовите ее из оконного менеджера.

// declaration
Screen &clear();

// definition
inline Screen &Screen::clear() {
    contents.resize(height * width, ' ');
    return *this; // return this object as an lvalue
}

// use
void Window_mgr::clear(ScreenIndex i)
{
    // s is a reference to the Screen we want to clear
    Screen &s = screens[i];
    // reset the contents of that Screen to all blanks
    s.clear();
}

Смотрите вживую в Coliru

person amdn    schedule 19.01.2016