The general rule is to make the code as simple as possible.
So, for example, if you have a string as class member, like the student's name, you would definitely use std::string, and not a smart pointer to it:
class Student {
...
std::string m_name; // *not* std::shared_ptr<std::string> m_name !!
};
On the other hand, if you need to represent shared ownership, like in your teacher-student example, using a std::shared_ptr would be a better choice:
class Student {
...
std::shared_ptr<Teacher> m_teacher;
};
In this case, a single Teacher instance is shared among different Students.
Moreover, if you can guarantee that the lifetime of the Teacher instance is appropriate, i.e. it's longer than that of its Students, you can even simply use a raw observing pointer:
class Student {
...
// Raw observing pointer to Teacher.
// Note: Pay attention to the Teacher instance lifetime.
Teacher* m_teacher;
};