Поведение функций-членов при прямом объявлении классов

Когда следующий код скомпилирован с помощью C++11, он ведет себя так, как ожидалось.

class Student;

class University
{
    vector <Student*> students;
public:
    University();
    void print();
};

class Student
{
    string name;
public:
    Student(string nom) : name(nom) {}
    friend ostream& operator << (ostream& out, Student* S)
    {
        return out << S -> name;
    }
};

University::University()
{
    for (string name: {"Alice", "Bob"})
        students.push_back(new Student(name));
}

void University::print() { for (auto s: students) cout << s << '\n'; }

int main()
{
    University uni;
    uni.print();
}

Распечатка

Alice
Bob

Однако, когда функция print() реализована внутри объявления класса следующим образом:

class University
{
    vector <Student*> students;
public:
    University();
    void print() { for (auto s: students) cout << s << '\n'; }
};

Распечатка становится

0x20055ff0
0x20056028

Вопрос: почему функция print() ведет себя так, хотя Student была объявлена ​​в самой первой строке?

Решение. Объявите все классы перед реализацией любой из их функций-членов. Пока print() и operator << реализованы после объявления их классов, print() будет работать правильно, даже если он реализован до operator <<.


person visitor    schedule 26.04.2018    source источник
comment
Как правило, я бы сказал, что лучше писать перегрузки оператора ‹‹ для ссылок на ваши объекты, а не для указателей. Тогда вы получили бы ошибку, что для вашего объекта не было оператора‹‹.   -  person Gem Taylor    schedule 27.04.2018
comment
@GemTaylor Если я напишу operator << перегрузки для Student&, то print() выведет шестнадцатеричные адреса; ошибки компиляции не будет. К сожалению, я должен использовать Student*, потому что vector <Student&> в классе University будет ошибкой компиляции.   -  person visitor    schedule 28.04.2018
comment
Но вы можете написать cout << *s . Честно говоря, использование необработанных указателей, подобных этому, само по себе является кошмаром, потому что теперь вы должны помнить об их удалении в своем деструкторе.   -  person Gem Taylor    schedule 30.04.2018
comment
Я принимаю вашу рекомендацию cout << *s, но есть ли способ избежать необработанных указателей, таких как Student*, когда vector не может содержать ссылки? У меня тоже не может быть vector <Student>, так как я не хочу, чтобы Student удалялись при удалении University.   -  person visitor    schedule 02.05.2018
comment
К сожалению, вы очень ограничены. Вы можете использовать интеллектуальные указатели, такие как std::shared_ptr и std::unique_ptr, но опять же для их определения требуется определение класса Student, и тогда вы также можете использовать вектор экземпляров Student. Если бы Student был базовым классом интерфейса, то было бы оправданным требовать, чтобы базовый класс интерфейса был определен до его использования.   -  person Gem Taylor    schedule 02.05.2018
comment
Вы можете определить unique_ptr с пользовательским аргументом уничтожения, который вы еще не определили. Сюрприз: когда вы его определяете, когда определен Student, он просто удаляет по умолчанию. Вы можете просто написать свой собственный класс-оболочку вокруг Student*, у которого есть деструктор. Оба они заключают с вами контракт на написание кода для удаления экземпляров.   -  person Gem Taylor    schedule 02.05.2018


Ответы (1)


Поведение по умолчанию при печати указателя с operator<< заключается в печати шестнадцатеричного представления адреса памяти, на который указывает указатель.

Когда реализация print() находится внутри объявления класса, компилятор еще не видел переопределения operator<< для указателя Student*, поэтому он не знает, что он должен вести себя иначе, чем по умолчанию.

Когда вы определяете реализацию print() после переопределения operator<<, компилятор знает, что нужно использовать это переопределение вместо значения по умолчанию.

person Christian.M    schedule 27.04.2018
comment
Если вы действительно хотите, вы можете предварительно объявить свой класс и свой оператор ‹‹, чтобы их можно было увидеть встроенными методами. - person Gem Taylor; 27.04.2018