Dynamically allocate memory for struct

前端 未结 8 613
自闭症患者
自闭症患者 2020-12-31 12:21

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

8条回答
  •  借酒劲吻你
    2020-12-31 12:45

    "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.

提交回复
热议问题