-2

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;
};
DLHazel
  • 3
  • 3
  • 4
    In the code you show there seems to be no need to dynamically allocate memory yourself. You already use a `string` which should take care of it itself. Though I don't understand why you store pointers in your class instead of whole objects – UnholySheep Sep 03 '18 at 20:19
  • You probably should consider using smart pointers. See [this](https://en.cppreference.com/w/cpp/memory) – Basile Starynkevitch Sep 03 '18 at 20:19
  • 2
    @Basile As Unholy says, you should consider not using pointers at all. –  Sep 03 '18 at 20:21
  • That's the thing, I have no idea why I'm required to do it this way, but I have been told that the strings need to be dynamically allocated. That's why I'm also trying to understand the reason why someone might prefer to do it this way. – DLHazel Sep 03 '18 at 20:22
  • 3
    A bizarre requirement. The vast majority of the point of `std::string` is to remove the responsibility for direct memory management from the programmer. Perhaps you should add the relevant portions of the assignment text to the question in case you have merely misinterpreted something. – user4581301 Sep 03 '18 at 20:29
  • It wasn't on the assignment, per se, but the h file was provided by the teacher. I was able to create most of the cpp just fine, but when I looked back the constructor, I realized there was an issue. I can't assigned a reference to a pointer, and when I asked about this, I was told I needed dynamically allocate memory for the strings. I would have normally inquired further on that, but it's Labor Day... – DLHazel Sep 03 '18 at 20:44
  • OK. You gotta do what you gotta do to pass the class. Answer (and an explanation of why you don't want to do this) up in a few moments. – user4581301 Sep 03 '18 at 20:46
  • There are probably no good reason to do it that way except that your teacher wants to see if you understand what you have already learned! – Phil1970 Sep 03 '18 at 20:59
  • 1
    @DLHazel *but I'm not understanding the use of it. Why use dynamically allocated memory?* -- The reason you're not understanding the use for dynamically allocated memory is that for your exercise, there is no need to use it. Thus it is reasonable for why you don't see the purpose of it. If you were actually being taught when and where to use dynamic memory allocation, then maybe you will understand why it exists. Now, create your own `vector` or `string` class, or do some things with `virtual` and polymorphism, *then* you will see the use of it. – PaulMcKenzie Sep 03 '18 at 21:47

1 Answers1

0

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

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

user4581301
  • 33,082
  • 7
  • 33
  • 54