"Динамически распределить ученика, а затем предложит пользователю ввести имя, фамилию и А-номер студента (идентификационный номер)."
Для этого задания необходимо иметьне полностью инициализированный объект Student
, пока вы не сможете обновить его информацией, предоставленной пользователем.В общем, это очень плохая идея, потому что простая возможность иметь не полностью инициализированный объект (например, в этом случае не хватает правильного значения id) делает код, использующий этот объект, более сложным, потому что он должен проверить, например, существует липравильное значение идентификатора.И эта сложность для правильного использования, а также неспособность понять, что сложность необходима для правильного использования, привлекает ошибки как безумные - нехорошо.
Именно поэтому C ++, расширяющий C, обеспечил очень сильную связь между выделением и инициализацией.С помощью выражения C ++ new
вы получаете либо и успешное выделение и успешную полную инициализацию, либо ни то, ни другое (оно очищается при сбое).Вот чему лучше учить этот вопрос!
Поэтому вместо приведенного выше вопроса я собираюсь научить вас приемлемой практике C ++ (хотя обычно следует избегать использования new
), что означает ответэтот измененный вопрос:
Запрашивает у пользователя имя, фамилию и А-номер студента (идентификационный номер), а затем динамически выделяет объект Student
с этими значениями.
ОК, вот так:
// 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.h> // assert
#include <iostream> // std::cout,std::endl
#include <string> // std::string
#include <sstream> // std::istringstream
#include <stdexcept> // std::exception, std::runtime_error
#include <stdlib.h> // 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;
}
Для каждого исполняемого выражения new
(которое выполняет распределение и инициализацию) в идеале должно быть соответствующее выполнение выражения delete
, которое очищаети освобождает блок памяти, чтобы его можно было использовать повторно.И выражение delete
в идеале должно выполняться, даже если что-то не получается и выдает исключение.Следовательно, try
и catch
.
Тем не менее, его кодирование может привести к ошибкам и многословно.
Вместо этого в более идиоматическом программировании на С ++ можно использовать умный указатель , объект, который содержит указатель и обеспечивает операции с указателями (таким образом, является указателем), а деструктор автоматически выполняет выражение delete
, когда указательбольше не используется.Стандартная библиотека C ++ имеет несколько таких классов интеллектуальных указателей.Как правило, используйте наиболее ограниченный интеллектуальный указатель, который вы можете использовать, поскольку он имеет наименьшие накладные расходы и, скорее всего, будет поддерживать преобразование в более общие интеллектуальные указатели, в то время как обратное гораздо менее вероятно, совершенно маловероятно.
Такв этом случае вы можете использовать, например, C ++ 11 std::unique_ptr
или, если ваш компилятор старый, C ++ 03 std::auto_ptr
, оба из заголовка <memory>
:
// 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.h> // assert
#include <iostream> // std::cout,std::endl
#include <memory> // std::unique_ptr
#include <string> // std::string
#include <sstream> // std::istringstream
#include <stdexcept> // std::exception, std::runtime_error
#include <stdlib.h> // 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<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 unique_ptr<Student>( new Student( id, firstName, lastName ) );
}
} // namespace blah
void cppMain()
{
using namespace blah;
unique_ptr<Student> 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;
}
Но, кромедля требования присвоения использовать динамическое распределение, программа с вышеуказанными функциональными возможностями была бы написана без какого-либо динамического выделения или интеллектуальных указателей.Функция studentFromInput
просто возвращает объект Student
по значению, копируя.Это почти парадокс, но современный C ++ очень сильно основан на копировании и все еще дает довольно быстрые программы!
Конечно, под капотом есть большое количество грязных уловок, чтобы избежать копирования на самом деле происходит в машинном коде.