I am taking a C++ class and have a assignment which requires me to dynamically allocate memory for a struct. I don\'t recall ever going over this in class and we only brief
"Dynamically allocate a student and then prompts the user for student’s first name, a last name, and A - number(ID number). "
This assignment requires you to have a not completely initialized Student
object around until you can update it with the information provided by the user. That is a very bad idea in general, because the mere possibility of having a not completely initialized object (e.g. in this case lacking a proper id value) makes the code using that object more complex because it has to check whether, for example, there is a proper id value. And that complexity for proper use, plus failures to recognize that the complexity is needed for proper use, attracts bugs like mad – ungood.
That is why C++, extending C, provided a very strong coupling between allocation and initialization. With a C++ new
expression you get either both a successful allocation and a successful complete initialization, or else neither (it cleans up on failure). That is what the question should better teach!
So instead of the given question quoted above, I'm going to teach you acceptable C++ practice (although using new
is generally to be avoided), which means answering this modified question:
Prompt the user for student’s first name, a last name, and A - number(ID number), and then dynamically allocate a
Student
object with these values.
OK, here goes:
// The Dynamic Student, version 1.
// "Prompt the user for student’s first name, a last name, and A - number
// (ID), and then dynamically allocate a `Student` object with these values."
#include // assert
#include // std::cout,std::endl
#include // std::string
#include // std::istringstream
#include // std::exception, std::runtime_error
#include // EXIT_SUCCESS, EXIT_FAILURE
#define CPP_NO_COPYING_OF( Clazz ) \
Clazz( Clazz const& ); \
Clazz& operator=( Clazz const& )
namespace cpp {
using namespace std;
bool hopefully( bool const c ) { return c; }
bool throwX( string const& s ) { throw runtime_error( s ); }
string lineFromInput()
{
string result;
getline( cin, result )
|| throwX( "lineFromInput: std::getline failed (EOF?)" );
return result;
}
string lineFromInput( string const& prompt )
{
cout << prompt;
return lineFromInput();
}
int intFromInput( string const& prompt )
{
istringstream stream( lineFromInput( prompt ) );
int result;
stream >> result
|| throwX( "intFromInput: input line was not a valid number spec" );
return result;
}
} // namespace cpp
namespace blah {
using namespace std;
using namespace cpp;
struct Student
{
CPP_NO_COPYING_OF( Student );
int const id;
string const firstName;
string const lastName;
Student(
int const _id,
string const _firstName,
string const _lastName
)
: id( _id ), firstName( _firstName ), lastName( _lastName )
{}
};
Student* studentFromInput()
{
cout << "It's -- the Dynamic Student program!" << endl;
string const firstName = lineFromInput( "First name, please? " );
hopefully( firstName != "" )
|| throwX( "Sorry, the first name can't be nothing." );
string const lastName = lineFromInput( "Last name, please? " );
hopefully( lastName != "" )
|| throwX( "Sorry, the last name can't be nothing." );
int const id = intFromInput( "And the student id is...? " );
hopefully( id > 0 )
|| throwX( "Sorry, the id can't be negative or zero." );
return new Student( id, firstName, lastName );
}
} // namespace blah
void cppMain()
{
using namespace blah;
Student const* const pStudent = studentFromInput();
try
{
// Use the student object, e.g.
cout
<< "The student is "
<< pStudent->firstName << " " << pStudent->lastName
<< ", with id " << pStudent->id << "."
<< endl;
// Then:
delete pStudent;
}
catch( std::exception const& )
{
delete pStudent;
throw; // Rethrows the exception.
}
}
int main()
{
using namespace std;
try
{
cppMain();
return EXIT_SUCCESS;
}
catch( exception const& x )
{
cerr << "!" << x.what() << endl;
}
return EXIT_FAILURE;
}
For each executed new
expression (which does allocation and initialization) there should ideally be a corresponding execution of a delete
expression, which cleans up and deallocates the memory block so that it can be reused. And the delete
expression should ideally be executed even if something fails and throws an exception. Hence the try
and catch
.
However, coding it like that is error prone and verbose.
Instead, in more idiomatic C++ programming one will use a smart pointer, an object that holds a pointer and provides pointer operations (so it looks like it is a pointer), and whose destructor automatically executes a delete
expression when the pointer is no longer used. The C++ standard library has several such smart pointer classes. As a general rule, use the most restrictive smart pointer that you can, because it has least overhead and will most likely support conversion to more general smart pointers, while the opposite is much less likely, downright unlikely.
So in this case, you can use e.g. C++11 std::unique_ptr
or if your compiler is old, C++03 std::auto_ptr
, both from the
header:
// The Dynamic Student, version 2 -- using smart pointer.
// "Prompt the user for student’s first name, a last name, and A - number
// (ID), and then dynamically allocate a `Student` object with these values."
#include // assert
#include // std::cout,std::endl
#include // std::unique_ptr
#include // std::string
#include // std::istringstream
#include // std::exception, std::runtime_error
#include // EXIT_SUCCESS, EXIT_FAILURE
#define CPP_NO_COPYING_OF( Clazz ) \
Clazz( Clazz const& ); \
Clazz& operator=( Clazz const& )
namespace cpp {
using namespace std;
bool hopefully( bool const c ) { return c; }
bool throwX( string const& s ) { throw runtime_error( s ); }
string lineFromInput()
{
string result;
getline( cin, result )
|| throwX( "lineFromInput: std::getline failed (EOF?)" );
return result;
}
string lineFromInput( string const& prompt )
{
cout << prompt;
return lineFromInput();
}
int intFromInput( string const& prompt )
{
istringstream stream( lineFromInput( prompt ) );
int result;
stream >> result
|| throwX( "intFromInput: input line was not a valid number spec" );
return result;
}
} // namespace cpp
namespace blah {
using namespace std;
using namespace cpp;
struct Student
{
CPP_NO_COPYING_OF( Student );
int const id;
string const firstName;
string const lastName;
Student(
int const _id,
string const _firstName,
string const _lastName
)
: id( _id ), firstName( _firstName ), lastName( _lastName )
{}
};
unique_ptr studentFromInput()
{
cout << "It's -- the Dynamic Student program!" << endl;
string const firstName = lineFromInput( "First name, please? " );
hopefully( firstName != "" )
|| throwX( "Sorry, the first name can't be nothing." );
string const lastName = lineFromInput( "Last name, please? " );
hopefully( lastName != "" )
|| throwX( "Sorry, the last name can't be nothing." );
int const id = intFromInput( "And the student id is...? " );
hopefully( id > 0 )
|| throwX( "Sorry, the id can't be negative or zero." );
return unique_ptr( new Student( id, firstName, lastName ) );
}
} // namespace blah
void cppMain()
{
using namespace blah;
unique_ptr const pStudent = studentFromInput();
// Use the student object, e.g.
cout
<< "The student is "
<< pStudent->firstName << " " << pStudent->lastName
<< ", with id " << pStudent->id << "."
<< endl;
}
int main()
{
using namespace std;
try
{
cppMain();
return EXIT_SUCCESS;
}
catch( exception const& x )
{
cerr << "!" << x.what() << endl;
}
return EXIT_FAILURE;
}
But, except for the assignment's requirement to use dynamic allocation, a program with the functionality above would be written without any dynamic allocation or smart pointers. The studentFromInput
function would just return a Student
object by value, copying. It is almost a paradox, but modern C++ is very heavily based on copying, and still yields pretty fast programs!
Of course, under the hood there are a large number of dirty tricks to avoid that the copying actually happens in the machine code.