简体   繁体   中英

Dynamically Allocated Memory Constructor

I'm trying to create a constructor in which the strings are dynamically allocated. I've looked up dynamically allocated memory several times and watched a video about it, but I'm still not 100% sure if I'm understanding the concept. I'm hoping an example specific to what I'm coding will help me out a bit.

These are the private variables I have in my h file:

string* tableID;
int numSeats;
string* serverName;

With that in mind, could someone tell me how I could dynamically allocate memory for the strings in this constructor?

Table::Table(const string& tableID, int numSeats, const string& serverName) {

}

Finally, I would greatly appreciate it if someone could tell me the purpose of dynamically allocated memory. I've see explanations on what dynamically allocate memory is, but I'm not understanding the use of it. Why use dynamically allocated memory? What are the benefits? What are the drawbacks? Thank you!

EDIT: I'm including the rest of the h file. Note that this wasn't created by me, so I can't make changes to it. I can only adhere to it in the cpp file.

#include <string>
#include "party.h"

using std::string;

class Table {

public:
   Table();
   Table(const string& tableID, int numSeats, const string& serverName);
   ~Table();
   const string* getTableID() const { return tableID; }
   int getNumSeats() const { return numSeats; }
   const string* getServerName() const { return serverName; }
   void decrementTimer() { timer--; }
   int getTimer() const { return timer; }
   void setTimer(int duration) { timer = duration; }
   const Party* getParty() { return party; }
   void seatParty(const Party* newParty);
   void clearTable() { party = nullptr; timer = 0; }

private:
   string* tableID;
   int numSeats;
   string* serverName;
   int timer;
   const Party* party;
};

The easiest way to get what you want is to take advantage of the Member Initializer List as this also solves the problem of having the parameters shadow the member variables of the same name.

Table::Table(const string& tableID, 
             int numSeats, 
             const string& serverName):
    tableID(new string(tableID)), 
    numSeats(numSeats), 
    serverName(new string(serverName))
{

}

Allocation is performed with the new operator. Later you will have to release the dynamically allocated memory with the delete operator. Here is documentation on new and the same for delete .

But the use a pointer requirement is bizarre as storing pointers to string makes everything else you with the class do orders of magnitude more difficult. This may be the point of the assignment, but there are better and less-confusing ways to teach this lesson.

The allocated string s must be released. The C++ idiom of Resource Allocation Is Initialization ( What is meant by Resource Acquisition is Initialization (RAII)? ) suggests you have a destructor to automate clean-up to ensure that it is done. If you need a destructor, you almost always need the other two members of The Big Three ( What is The Rule of Three? ) and possibly need to take The Rule of Five into account as well.

Whereas because string observes the Rule of Five for you, you should be able to take advantage of the Rule of Zero and implement no special functions.

MM raises an excellent point in the comments. The above example is too naive. It is probably all you need for the assignment, but it's not good enough for real code. Sooner or later it will fail. Example of how it fails.

First we replace string with something that can expose the error:

class throwsecond
{
    static int count;
public:
    throwsecond(const string &)
    {
        if (count ++)
        {
            count = 0; // reset count so only every second fails
            throw runtime_error("Kaboom!");
        }
        cout << "Constructed\n";
    }
    ~throwsecond()
    {
        cout << "Destructed\n";
    }
};

int throwsecond::count = 0;

Then a simple class that does basically the above with less frills

class bad_example
{
    throwsecond * a;
    throwsecond * b;
public:
    bad_example(): a(nullptr), b(nullptr)
    {
    }
    bad_example (const string& a,
                 const string& b)
    {
        this->a = new throwsecond(a);
        this->b = new throwsecond(b);
    }
    ~bad_example()
    {
        delete a;
        delete b;
    }
};

and a main to exercise it

int main()
{
    cout << "Bad example\n";
    try
    {
        bad_example("", "");
    }
    catch (...)
    {
        cout << "Caught exception\n";
    }
}

Output:

Bad example
Constructed
Caught exception

We have an object constructed and never destroyed.

Since a default constructor has been defined by Table we can, with a compiler that supports the C++11 or a more recent Standard, take advantage of delegated constructors to force destruction of the partially constructed object because it has been fully constructed by the default constructor.

class good_example
{
    throwsecond * a;
    throwsecond * b;
public:
    good_example(): 
        a(nullptr), b(nullptr) //must be nulled or destruction is dicey
    {
    }
    good_example (const string& a,
                  const string& b) : good_example() // call default constructor
    {
        this->a = new throwsecond(a);
        this->b = new throwsecond(b);
    }
    ~good_example()
    {
        delete a;
        delete b;
    }
};

Output:

Good example
Constructed
Destructed
Caught exception

One construct and one destruct. The beauty of this approach is it scales well and adds nothing to the code that you don't already have. The cost is minimal, a and b get initialized and then assigned as opposed to just initialization. Faster code is useless if it doesn't work.

Full example: https://ideone.com/0ckSge

If you can't compile to a modern standard, you wind up doing something like the next snippet to make sure everything is deleted. It's main sin is it's ugly, but as you add more classes that must be constructed and destroyed it starts getting unwieldy.

Table::Table(const string& tableID, 
             int numSeats, 
             const string& serverName):
    tableID(NULL), 
    numSeats(numSeats),
    serverName(NULL)
{
    try
    {
        this->tableID(new string(tableID)), 
        // see all the this->es? don't shadow variables and you won't have this problem
        // miss a this-> and you'll have a really bad day of debugging
        this->serverName(new string(serverName))
        // more here as required
    }
    catch (...)
    {
        delete this->tableID;
        delete this->serverName;
        // more here as required
        throw;
    }
}

There is probably a way to improve on this and make it more manageable, but I don't know it. I just use newer standards and value semantics (I'd love it if someone can provide a good link that describes this concept) where possible.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM