Database of students
This code is not complete so difficult to give a full review, but here are my thoughts.

Student

Put public methods first

This point is debatable, but I generally think it's better to have public methods before private variables. This is the only thing users of your class should care about, so make it easier to find.

Use variable name forename

forename is a better variable name than firstName, and fits better with surname (as apposed to lastName).

Type alias std::string

I think it would be clearer to type alias std::string for the names, if you make this public, the database class can use this alias to make the map definition clearer too:

using NameType = std::string;


Add missing constructors

Not sure if these were left out intentionally, but a simple forwarding constructor here will suffice:

template &lt;typename T1, typename T2&gt;
Student(T1&amp;&amp; forename, T2&amp;&amp; surname, long studentNumber);


Make fullName a non-member

It's generally preferable to make your class interface as minimal as possible, and you really gain nothing here by having this as a member method, so make it a non-member:

Student::NameType getFullName(const Student&amp; student);


Return fields by const &amp;

You are making a copy on every call to getFirstName and getSurname, better to return a const reference - the caller can always make a copy if needed.

Database

Don't make Database responsible for reading user input

Your remove functions should not read anything from the user, it obscures the interface and purpose of the class. If you need this functionality, put it into a non-member method:

long getStudentNumberToRemoveFromUser();


Consider a better design

I'd say your design is flawed; you shouldn't be removing Student objects by name (not even full name), as you could have duplicates. Presumably the student number is unique, so this should be the only identifier used for removal. Instead you could offer a public method to gain access for student numbers. So you just have:

std::vector&lt;Student&gt; getMatchingStudents(Student::NameType surname forename, Student::NameType surname) const;
bool remove(const Student&amp; student);


Where the return bool indicates if the record was removed.

Consider Boost.Bimap

Boost.Bimap already solves the two way access problem you are having.

Remove shared_ptr

Assuming you don't want to use Boost.Bimap.

There's really no need to use shared_ptr here; Database has complete control over the lifetime of the Student objects. Introducing the shared_ptr only adds code complexity, additional memory consumption, and confusion. Instead you can either store everything by value (completely feasible for just 2000 records), or store values in mapByStudentNumber and store a raw pointer, or std::reference_wrapper&lt;const Student::NameType in mapByName.

Use better variable names

There's some debate about how to best name maps (xToY, ys, xToYMap etc), but I don't like mapByName and mapByStudentNumber. I'd probably go with namesToNumbers and numbersToNames.