Constructors and operator= with dynamic memory allocation - c++

noob here. I'm doing an exercise from a book and the compiler reports no errors, but the program crashes when I try to run it.
I'm trying to run a little program exercising the different methods of the Cow class. It explicitly has: a constructor, a default constructor, a copy constructor, a destructor, an overloaded assignment operator, and a method to show its contents.
I'll put the whole project:
Class specification:
//cow.h -- For project Exercise 12.1.cbp
class Cow
{
char name[20]; // memory is allocated in the stack
char *hobby;
double weight;
public:
Cow();
Cow(const char *nm, const char *ho, double wt);
Cow(const Cow &c);
~Cow();
Cow & operator=(const Cow &c);
void ShowCow() const; // display all cow data
};
Methods implementation:
// cow.cpp -- Cow class methods implementation (compile with main.cpp)
#include <cstring>
#include <iostream>
#include "cow.h"
Cow::Cow() // default destructor
{
strcpy(name, "empty");
hobby = new char[6]; // makes it compatible with delete[]
strcpy(hobby, "empty");
weight = 0.0;
}
Cow::Cow(const char *nm, const char *ho, double wt)
{
strcpy(name, nm); // name = nm; is wrong, it copies the address of the argument pointer (swallow copying)
/*if (name[20] != '\0') // if it's not a string, make it a string (in case nm is larger than 20)
name[20] = '\0';*/
hobby = new char[strlen(ho) + 1]; // allocates the needed memory to hold the argument string
strcpy(hobby, ho); // copies the pointed-to data from the argument pointer to the class pointer
weight = wt;
}
Cow::Cow(const Cow &c) // copy constructor
{
strcpy(name, c.name); // copies the value to the desired address
char *temp = hobby; // stores the address of the memory previously allocated with new
hobby = new char[strlen(c.hobby) + 1];
strcpy(hobby, c.hobby); // copies the value to the new address
delete[] temp; // deletes the previously new allocated memory
weight = c.weight;
}
Cow::~Cow()
{
delete[] hobby;
}
Cow & Cow::operator=(const Cow &c) // overloaded assignment operator
{
strcpy(name, c.name);
char *temp = hobby;
hobby = new char[strlen(c.hobby) + 1];
strcpy(hobby, c.hobby);
delete[] temp;
weight = c.weight;
return *this;
}
void Cow::ShowCow() const
{
std::cout << "Name: " << name << '\n';
std::cout << "Hobby: " << hobby << '\n';
std::cout << "Weight: " << weight << "\n\n";
}
Client:
// main.cpp -- Exercising the Cow class (compile with cow.cpp)
#include "cow.h"
#include <iostream>
int main()
{
using std::cout;
using std::cin;
Cow subject1; // default constructor
Cow subject2("Maria", "Reading", 120); // non-default constructor
Cow subject3("Lula", "Cinema", 135);
subject1 = subject3; // overloaded assignment operator
Cow subject4 = subject2; // copy constructor
subject1.ShowCow();
subject2.ShowCow();
subject3.ShowCow();
subject4.ShowCow();
cin.get();
return 0;
}
I was hiding some parts of the code to locate the possible problem and it seems the progam doesn't like this two lines:
subject1 = subject3;
Cow subject4 = subject2
And in particular, in the overloaded assignment operator and the copy constructor, if I hide the delete[] temp line, the program doesn't crash.
I'm total noob and probably is something stupid but I can't see what I'm doing wrong in these definitions.
Any help?

Cow::Cow(const Cow &c) // copy constructor
{
strcpy(name, c.name); // copies the value to the desired address
char *temp = hobby; // stores the address of the memory previously allocated with new
hobby = new char[strlen(c.hobby) + 1];
strcpy(hobby, c.hobby); // copies the value to the new address
delete[] temp; // deletes the previously new allocated memory
weight = c.weight;
}
It's the copy c-tor. There is no previously allocated members. this->hobby points to random garbage. So when you delete[] temp (i.e. this->hobby before the new assignment), you get UB.
Cow & Cow::operator=(const Cow &c) // overloaded assignment operator
{
strcpy(name, c.name);
char *temp = hobby;
hobby = new char[strlen(c.hobby) + 1];
strcpy(hobby, c.hobby);
delete[] temp;
hobby = name;
weight = c.weight;
return *this;
}
hobby = name is incorrect, as it causes a memory leak + destructor will try to delete an object that was not allocated with operator new[].

#ForEveR Already noted that your copy constructor is deleting memory that was never allocated.
But I'm going to argue that the entire exercise is flawed and is teaching C with classes, NOT C++. If you used string all your problems would go away because you wouldn't have to manage resources directly. This eliminates the need for you to write a destructor or copy assignment/constructor entirely. For example:
class Cow
{
std::string name;
std::string hobby;
double weight;
public:
Cow();
Cow(const std::string& nm, const std::string& ho, double wt);
void ShowCow() const; // display all cow data
};

Related

operator(+) to concatenate text in char array results in memory leak

I have a class that contains the operator + () to add 2 objects. The class contains an attribute char * variable str that points to an allocated char array from new char[] that contains a C style text string. I want the operator +() to concatenate two char arrays into a third char array allocated as a new buffer sized to contain the concatenated string as the result of the operator.
I am using overloaded constructor and initializing list and I initialize the variable to a default value. The destructor I've added to the class frees the memory allocated for the object text buffer in the char *str variable.
Everything works perfectly fine WITHOUT the class destructor, but as soon as I add the destructor, the program prints weird characters. I am also seeing a memory leak.
Here is the code and note that the class destructor is included here!
#include <iostream>
#include <cstring>
#include<cstring>
class Mystring {
private:
char *str;
public:
Mystring():
str{nullptr} {
str = new char[1];
*str = '\0';
}
Mystring(const char *s):
str {nullptr} {
if (s==nullptr) {
str = new char[1];
*str = '\0';
} else {
str = new char[std::strlen(s)+1];
std::strcpy(str, s);
}
}
Mystring(const Mystring &source):
str{nullptr} {
str = new char[std::strlen(source.str)+ 1];
std::strcpy(str, source.str);
std::cout << "Copy constructor used" << std::endl;
}
~Mystring(){
delete [] str;
}
void print() const{
std::cout << str << " : " << get_length() << std::endl;
}
int get_length() const{
return std::strlen(str);
}
const char *get_str() const{
return str;
}
Mystring&operator+(const Mystring &dstr)const;
};
// Concatenation
Mystring &Mystring::operator+(const Mystring &dstr)const {
char *buff = new char[std::strlen(str) + std::strlen(dstr.str) + 1];
std::strcpy(buff, str);
std::strcat(buff, dstr.str);
Mystring temp {buff};
delete [] buff;
return temp;
}
int main() {
Mystring m{"Kevin "};
Mystring m2{"is a stooge."};
Mystring m3;
m3=m+m2;
m3.print();
return 0;
}
Mystring &Mystring::operator+(const Mystring &dstr)const
This states that it's going to return a reference to a MyString Your temp however is local. This results in a far more serious issue than just a memory leak.
Once you're returning by value; you'll find that it might end up wanting to use the copy or move constructor; which leads to (as said in the comments) the rule of 3 (or rule of 5).
This
Mystring &Mystring::operator+(const Mystring &dstr)const {
char *buff = new char[std::strlen(str) + std::strlen(dstr.str) + 1];
std::strcpy(buff, str);
std::strcat(buff, dstr.str);
Mystring temp {buff};
delete [] buff;
return temp;
}
contains a bug. You create a temp object and return it by reference (Mystring&). That reference turns into a dangling one as soon as the function returns and using it is undefined behaviour. The fact that the code appears to work with or without the destructors means nothing. Undefined behaviour is undefined and you cannot expect anything from the code that invokes it.
You may simply change the return type of Mystring::operator+ to return by value (simply Mystring).
Although, you would encounter another problem later on.
m3=m+m2;
performs a copy assignment, which uses T& operator = (const T&). You did not provide one, so the compiler generated one.
Why is that bad? Because automatically generated special member functions perform shallow copies (if they are used for copying), which means that if you have, for example, pointer member variable, the pointer will be copied. Just the pointer. Not the data it points to. Just the pointer. You end up with two pointers pointing to the same memory and once a correctly defined destructor tries to free those resources, you encounter a double delete[] error, which, again, is undefined behaviour for non-nullptr pointers.
You may want to look forward into the rule of three, and then probably into the rule of five.

Member Variables and Functions through this pointers

#include <iostream>
#include<cstring>
using namespace std;
class String
{
private:
char *sptr;
public:
String()
{
}
String(char str[])
{
sptr = new char[strlen(str) + 1];
strcpy( sptr, str );
}
String(const String& source)
{
sptr = new char[strlen(source.sptr) + 1];
strcpy( sptr, source.sptr);
}
~String()
{
delete sptr;
}
String operator=( const String& other )
{
if(&other!=NULL)
{
String tmp( other );
sptr = new char[strlen(tmp.sptr) + 1];
strcpy( sptr, tmp.sptr);
}
return *this;
}
void display()
{
for( char const* p = sptr; *p != '\0'; ++ p) {
std::cout << *p;
}
cout<<endl;
}
};
int main()
{
String a1;
String a2("Hello ");
String a3(a2);
a2.display();
a3.display();
//a2.change("Java ");
a2.display();
a3.display();
}
The Output pf the program is
Hello Hello Hello Hello.
But I am undone to do the following changes in my code... i.e.. In Main () I created the following objects
String a1;
String a2("Hello ");
String a3(a2);
a2.display();
a3.display();
//a2.change("Java ");
a2.display();
a3.display();
I want to change the the object a2(commented) with Java and desired the output as
Hello Hello Java Hello
through deep copy of the Data members through this pointers.
How can I change the Hello string to java
Your String class cannot be used to build a program upon, due to the faults within the String class. You must fix those errors first before even thinking about using String as a string class.
Therefore this answer addresses how to fix String first -- from there, you should now be able to write the program knowing you're not working on a faulty foundation.
Error 1: Default construction
You failed to initialize sptr when a String is default constructed. Thus when ~String() is executed, the call to delete will attempt to delete an uninitialized pointer.
Simple 1 line main functions such as this one:
int main()
{
String s;
} // <-- destructor of s may crash
will show undefined behavior and may crash, as shown by this example.
To fix this:
class String
{
public:
String() : sptr(nullptr) {}
//...
};
Now when delete [] is issued on sptr, no harm will occur since it is perfectly valid to delete [] a nullptr (it becomes basically a no-op).
Error 2: Faulty assignment operator
Your String::operator= fails to deallocate the previous memory that was allocated. In addition, the check for NULL is incorrect, since &other will always be true.
I am assuming this is the check for self-assignment, but it is not written correctly. The check for self-assignment is done by comparing the address of the current object (this) with the address of the object being passed in:
if (this != &other)
Once you have that, then you could have written the rest of the function this way:
if(this != &other)
{
String tmp( other );
std::swap(tmp.sptr, sptr);
}
return *this;
All this function does is copy other to a temporary String called tmp, swap out the sptr with the tmp.sptr and let tmp die off with the old data. This is called the copy / swap idiom, where you're just swapping out the contents of the current object with the contents of the temporary object, and letting the temporary object die off with the old contents.
Note that the check for self-assignment isn't necessary when using copy / swap, but there is no harm done in making the check (could even be a small optimization done when the check for self-assignment is present).
Edit: The other issue is that operator= should return a reference to the current object, not a brand new String object. The signature should be:
String& operator=( const String& other ) // <-- note the return type is String&
{
//... code
//...
return *this;
}
Error 3: Wrong form of delete
Since you allocated using new [], you should be using delete [], not just delete in the destructor:
~String()
{
delete [] sptr;
}
Given all of these changes, the following code no longer has issues, as shown by the Live Example here.
Now that you have a working String class, then you can build your application from there.
As to your program, you can easily change your string by using the assignment operator. There is no need for a change() function:
String a2("Hello ");
a2.display();
a2 = "Java ";
a2.display();
Since String::operator=(const String&) is no longer faulty with the changes above, assignment can now be done without issues.

C# style string implementation in c++

I'm trying out a C# style string implementation in C++.
I have created an object and a pointer for class String, and assigned the object to the pointer. When i try to modify the object via the pointer instead of modifying the existing object i want to create a new object and make the pointer point it.
So i have overloaded the "=" operator, and creating a new object in the operator overloaded method. In order to reflect the change i need to use ss=*ss = "name";
Any suggestion to improve this code.
Below is my sample code
class String
{
char *str;
public:
String(char* str_in)
{
str = new char[strlen(str_in)];
strcpy(str, str_in);
}
String* operator=(char* s)
{
return new String(s);
}
};
int main()
{
String s="sample";
String *ss;
ss = &s;
ss=*ss = "name";
return 0;
}
I also tried to modify the this pointer, but not working as expected
String *ptr;
ptr = const_cast<String*>(this);
ptr = new String(s);
I would recommend some changes like this:
#include <string.h>
class String
{
char *str;
public:
String(const char* str_in)
{
str = new char[strlen(str_in)];
strcpy(str, str_in);
}
~String()
{
delete [] str;
}
String& operator=(const char* s)
{
char* tmp = new char[strlen(s)];
strcpy(tmp, s);
delete [] str;
str = tmp;
return *this;
}
};
int main()
{
String s("sample");
String *ss;
ss = &s;
ss = new String("name");
delete ss;
return 0;
}
First of all, you need an appropriate destructor or you are going to have a memory leak when String gets destroyed. Deleting the char* fixes this (since it is an array, we use the array delete).
Secondly, in C++, we almost always return a reference for operator= (not a pointer). So this revised operator= function is probably better - it deletes the old string, allocates memory for the new string, copies the new string, and returns *this.
Third, you can use const char* instead of char* for the constructor and assignment operator since you are not editing it.
In main(), I also created a new object for the pointer to point to since you requested that in the original post (and then it is deleted afterwards to avoid a memory leak).
Let me know if you have any questions with those changes.

Memory leaks in copy constructor

I have tried to read lots of solutions to memory leaks occur in copy constructor, but i still didnt understans how to solve it.
For example i have a "Person" class that has those mambers and functions (header files):
#include "Object.h"
#include <string.h>
#include <iostream>
using namespace std;
class Person: public Object
{
private:
char * p_name;
int length;
public:
virtual Object * copy() const;
virtual void print(ostream & os) const;
Person(char * name);
Person(const Person & per);
~Person();
};
In this program Im trying to enter "Objects" to a Vector while Person and Vector inherit from Object.
In both of the copy const i have memory leak problems (the program is working excellent).
For example in this code im getting a memory leaks of all of those 5 char arrays. I have more problems also in Vector memory leaks, but lets start in this simple code in the main (that occurs 5 memory leaks of the char array):
int main ()
{
const int SIZE = 5;
Person* persons[SIZE];
int i;
// preparation of name array
for (i = 0; i<SIZE; i++) {
char* tmp = new char[10];
sprintf(tmp, "P-%d", i);
persons[i] = new Person(tmp);
}
for (i = 0; i < SIZE; i++)
delete persons[i];
return 0;
}
Person class is:
#include "Person.h"
using namespace std;
Object * Person::copy() const
{
Person * p = new Person(*this);
return p;
}
void Person::print(ostream & os) const
{
for (int i = 0; i < this->length-1; i++)
{
os << this->p_name[i];
}
}
Person::Person(char * name)
{
delete this->p_name;
this->length = strlen(name)+1;
p_name = new char[length];
strncpy(p_name, name, length);
}
Person::Person(const Person & per)
{
delete[] this->p_name;
this->length = strlen(per.p_name) + 1;
this->p_name = new char[this->length];
strncpy(this->p_name, per.p_name, this->length);
}
Person::~Person()
{
delete[] this->p_name;
}
I would be thankful for your help!!
In main(), the tmp char array is not deleted, and that's the first memory leakage I'm seeing.
In the Person(char * name) constructor you call a delete on
Person::Person(char * name)
{
delete this->p_name;
the p_name is not allocated, so the behavior is undefined. And p_name is an array, so delete[] should be used.
if you use the std::string class, you can at least avoid the confusion between delete and delete[]
You don't have just a memory leak. You have a full-fledged, memory-corrupting, undefined behavior:
Person::Person(char * name)
{
delete this->p_name;
This is your constructor. The p_name class member does not appear to be initialized in any way. And the first thing you do, is you try to delete it.
So, whatever random value p_name will contain, as a result of the most recent set of cosmic rays striking the RAM capacitors that hold its initial value, the first thing the constructor does is try to free the pointed-to memory.
Before you can worry about any alleged leaks from your copy constructor, you need to fix a bunch of problems, like this, elsewhere.
in main(), where you prepare your c you allocate 5 char buffers, that're never freed
also (not speaking of std::string which would help you with your string stuff) , get rid of the unneeded "this->"s:
Person::Person(const Person & per)
{
delete[] this->p_name;
this->length = strlen(per.p_name) + 1;
this->p_name = new char[this->length];
strncpy(this->p_name, per.p_name, this->length);
}
could look like this:
Person::Person(const Person & per)
{
delete[] p_name;
length = strlen(per.p_name) + 1;
p_name = new char[length];
strncpy(p_name, per.p_name, length);
}
There are some problems with delete.
In your main, in the first while you have to delete temp; in the second well just don't use it, use delete [] persons.
In your copy constructor don't use delete this->p_name, that's not correct.
At first you have to set the pointer to null so p_name=NULL, and then use setters and getters, same for your value constructor.
And in your destructor, test if p_name exists before deleting it.

C++: pointer being freed was not allocated

I am cleaning up a toy program I wrote for a class using XCode 5. Part of the assignment was to gain familiarity with dynamic memory allocation (meaning it must use new and delete despite the fact that it would really work fine without it). I am receiving:
HeapOfStudents(67683,0x7fff769a6310) malloc: *** error for object 0x100300060: pointer being freed was not allocated *** set a breakpoint in malloc_error_break to debug
The error happens:
Student::~Student() {
delete firstName; // <- on this line
delete lastName;
delete address;
delete birthDate;
delete gradDate;
delete gpa;
delete crHours;
}
which is being called...
int main() {
string line = "";
vector<Student> students;
//
//Create new students from file
//
ifstream data_file ("heapData");
if (data_file.is_open()) {
while(getline(data_file, line))
students.push_back(*new Student(line)); <- inside this call here
data_file.close();
}
else return 0;
The full Student class is below.
Importantly, I noticed that while each line gets correctly pushed to the vector, when the next Student gets pushed, the previous Student inside the vector gets corrupted. I see the malloc error always on the third iteration of the loop (the file has 50 lines).
The program runs without exception if I change vector<Student> to vector<Student *> and student.push_back(*new Student(line)) to student.push_back(new Student(line)).
My question then is: Can someone please explain what, on a lower level, is happening inside this loop that creates this error?
My guess is that *new Student(line) uses the same pointer each time, so through each iteration, data gets corrupted because it is being freed (although this explanation raises questions), while new Student returns a new pointer each time, preserving the data that was passed to students. I'm thinking that maybe I need to write a copy constructor... This is just my guess.
Here is the Student class. Address and Date are also custom classes that are fairly similar. I didn't include them because they don't seem to be part of the issue.
//Constructors
Student::Student() {
firstName = new string("Testy");
lastName = new string("Testerson");
address = new Address("000 Street St", "", "Citytown", "State", "00000");
birthDate = new Date("01/02/9876");
gradDate = new Date("01/02/6789");
gpa = new string("10.0");
crHours = new string("300");
}
Student::Student(string FN, string LN, string L1, string L2, string C, string S, string Z, string BD, string GD, string GPA, string Hr) {
firstName = new string(FN);
lastName = new string(LN);
address = new Address(L1, L2, C, S, Z);
birthDate = new Date(BD);
gradDate = new Date(GD);
gpa = new string(GPA);
crHours = new string(Hr);
}
Student::Student(string line) {
set(line);
}
//Destructors
Student::~Student() {
delete firstName;
delete lastName;
delete address;
delete birthDate;
delete gradDate;
delete gpa;
delete crHours;
}
//Member Functions
void Student::set(string line) {
firstName = new string(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
lastName = new string(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
address = new Address();
address->setLine1(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
address->setLine2(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
address->setCity(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
address->setState(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
address->setZip(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
birthDate = new Date(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
gradDate = new Date(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
gpa = new string(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
crHours = new string(line.substr(0, line.find_first_of(",")));
line = line.substr(line.find_first_of(",") + 1, string::npos);
}
void Student::printReport() {
cout << *lastName << ", " << *firstName << ", " << address->getAddress()
<< ", " << birthDate->getDate() << ", " << gradDate->getDate()
<< ", " << *gpa << ", " << *crHours << endl;
}
void Student::printName() {
cout << *lastName << ", " << *firstName << endl;
}
string Student::getName() {
return string(*lastName + ", " + *firstName);
EDIT Here's the copy constructors and assignment operators I wrote for the student class should anyone be interested in seeing/critiquing them:
Student::Student(const Student & student){
firstName = new string(*student.firstName);
lastName = new string(*student.lastName);
address = new Address(*student.address);
birthDate = new Date(*student.birthDate);
gradDate = new Date(*student.gradDate);
gpa = new string(*student.gpa);
crHours = new string(*student.crHours);
}
Student& Student::operator=(const Student & student) {
return *new Student(student);
}
It looks like you're not following the Rule of Three, so Bad Things will happen if you copy a Student object. Specifically, both will contain pointers to the same objects, which both will try to delete - so one will try to delete something that the other has already deleted.
The easiest way to give the class valid copy semantics is to disallow copying, by deleting the copy constructor and copy-assignment operator:
class Student {
Student(Student const &) = delete;
void operator=(Student const &) = delete;
// rest of class...
};
Otherwise, if you want the class to be copyable, implement these to do something sensible.
Also, this causes a memory leak:
students.push_back(*new Student(line));
by dynamically creating a Student, copying it into the vector, and discarding the only pointer to the dynamic object. If you're storing objects, then push a copy of a temporary:
students.push_back(Student(line));
Alternatively, you could change the container type to vector<Student*>, and remember to delete each object when you remove its pointer. (That's a reasonable thing to do as an exercise in pointer-wrangling, as you say this is, but don't do it in any program you want to maintain.)
Once you've learnt the grisly details of manual memory management, make life easier for yourself by avoiding dynamic allocation unless absolutely necessary and, when you really do need it, always managing it with smart pointers, containers, and other RAII types, not by juggling raw pointers.
The issue is likely happening because the default copy and assignment operators do not do the right thing -- they copy the pointers. So if you create a temporary Student somewhere you are going to delete the pointers when that temporary is destructed, while the original still points to the same (now deleted) objects. When it is destructed, it is going to try to delete pointers that were already deleted, hence the error.
You need to follow the rule of three: if you need a custom destructor, assignment operator, or copy constructor, you need all of them.
Add the following public members to your class:
Student(Student const &);
Student & operator=(Student const &);
And define them like so:
Student::Student(Student const & other)
{
firstName = new string(other.firstName);
// And so on, for each pointer member.
}
Student & Student::operator=(Student const & other)
{
*firstName = other.firstName;
// And so on, for each pointer member.
return *this;
}
Note that all of this can be avoided by using string firstName; instead of string * firstName; -- in that case, the default destructor, copy constructor, and assignment operator would do the right thing and you wouldn't need to define any of them.
Further, be aware that your Address and Date classes could have the same problem! You have to do these kinds of gymnastics any time you use raw pointers as class members.
One problem you have is that you are keeping a vector of Student objects, instead of a vector of pointers to dynamically allocated objects (of type Student* therefore).
Simply replace your students variable to std::vector<Student*> students;. From then, you can simply push_back the pointer created by new.
The thing is that vector already deals with memory allocation, so the line making the push (that you highlighted in the code) was making a copy of the Student to a position in the vector. The pointer to the dinamically allocated object would become unreachable after that.
As Barmar pointed out, having a vector of pointer also has the advantage of being able to push pointers of subclasses of Student. A simple example on that:
class PhDStudent : public Student { ... }
students.push_back(new PhDStudent(...));
Furthermore, there are many other tweaks you should consider in your class:
Your constructor is taking string parameters by-value, which means they are deeply copied from their origin. Using const string& is preferable here, to avoid unnecessary copies.
As already pointed out by some other answers (by Mike Seymour, cdhowie, Anton Savin and whoever is going to point this out), you should follow the Rule of Three. If you want your class to be copiable, you should also implement the copy constructor and the copy assignment operator. If you're using C++11, you can take advantage of a move constructor and a move assignment as well, so as to reduce the number of allocations when moving those objects.
It may happen that you don't want Students to be copied, but you still want to put them into a vector. In C++11 it can be solved by just adding a move constructor:
class Student {
public:
Student() { ...}
~Student() {...}
Student(const Student&) = delete; // optional: will be deleted implicitly
Student(Student&& other) {
firstName = other.firstName;
other.firstName = nullptr;
// ...
}
};
This way copy constructor and assignment operator will be implicitly deleted, so you'll not be able to make copies. But vector and other containers will use move constructor just fine. This of course puts certain limitations on Student usage.
vector<Student> students;
students.push_back(Student()); // OK, student moves
Student s;
students.push_back(s); // Error: copy constructor for Student is deleted