Someone looking at my code today told me that I didn't need to delete a pointer I declared in one place because I didn't create it using the "new" operator and went on to say that you only have to delete them when you use the "new" operator to declare them. Is this accurate? Relevant code included, the pointer is to a vector object within a struct named "pParams".

/*use parameter name to determine which member of the
pParam struct should be affected*/
void Cfg::proc_param(string name, string value)
{
    vector<string> *param_vector;

    bool bad_param_name = false;
    if (name == "dirs_recursive")
        param_vector = &pParams.dirs_recursive;
    else if (name == "dirs_nonrecursive")
        param_vector = &pParams.dirs_nonrecursive;
    else if (name == "files")
        param_vector = &pParams.files;
    else
        cout << "Invalid configuration paramter " << name << endl;
        bad_param_name = true;

    delete param_vector;
}

Recommended Answers

All 7 Replies

He's basically right. But I don't like his or your terminology.

A pointer stores a memory address. When you use the new operator, you allocate a chunk of memory (either for a single object or for an array of objects) and it returns the address of the allocated memory, which you would store in a pointer. When you are done using that memory, you must deallocate it, which you do by calling delete on that address (pointer). So, only and all memory that was allocated with new must be deallocated with delete.

So, if you get a pointer by getting the address of an existing object, as in, param_vector = &pParams.files;, then you should not use delete on that pointer, ever. Doing so will either cause a crash or a heap-corruption (which will go silent at first, but could quite bad later).

BTW, there is a bug in your code (beside the delete statement), you need brackets for your last else-case:

else {
    cout << "Invalid configuration paramter " << name << endl;
    bad_param_name = true;
};

I believe I understand now. Also, nice catch on the missing brackets. Much obliged.

While the point about your code was broadly speaking correct, the explanation given is a bit off. It isn't so much how the pointer was declared or assigned as much as it is about how the memory it points to was allocated, and how long you need that memory to stick around.

The first thing you need to know is that in a program, there are four different ways memory can be allocated to the program. The first is read-only memory, which is where things like constants and string literals are stored; on modern systems, trying to change something in read-only memory will result in a protection fault. The next kind of memory is global memory, where global variables are set up. This is a fixed amount of space given to the program when it starts, and cannot be increased or decreased.

The third kind of memory is local memory, which is automatically allocated when a function begins and deallocated when it returns. While there are a few different ways that this could be implemented, it is almost invariably done using a stack, a data structure so ubiquitous that all but a handful of computers these days have built-in hardware support for it. A detailed explanation of how allocating memory on the stack works can be found here, but the details aren't terribly relevant right now. Suffice it to say, you can have a pointer to any of these three types of memory, but you should never apply the delete operator to a pointer that is pointing to any one of them.

This brings us to the last kind of memory, the heap or dynamic memory. This a region of memory that can be allocated to programs in pieces, at run time, without being automatically reclaimed when the function that allocated it ends (some languages have a special means of reclaiming dynamic memory after it is no longer in use called garbage collection, but that's not relevant here). In C++, the new operator is a request to the system to allocate enough memory to hold one or more objects of a given type or class; it returns a pointer to the newly allocated memory space, and in the case of most objects, it also calls the class constructor automagically to ensure that the object has a sensible set of values in it. The delete operator returns the allocated memory to the system. In between new and delete, however, you can pass the address of that memory around from one pointer to another, treating it as if it were the same as a pointer to any other non-constant memory.

There are just three rules to follow with dynamic memory: don't lose the address, or you won't be able to deallocate later; always remember to deallocate it after you are done with it; and don't try to access the memory after it has been deallocated. These rules are trickier to follow than they sound, however, as it is easy to lose track of where the memory is and how you are using it at any given time (this is the reason why languages like Java and Python use garbage collection - the programmer doesn't have to keep track of these details, the system does it for them).

So, if it is so complicated, why bother with dynamic memory? Because you can make arrays of arbitrary size without knowing how big it has to be beforehand. Also, you can use it to allocate structures with pointers in them, which can then point to other dynamically allocated memory, letting you chain the allocated memory into more complex data structures such as lists and trees. It simply wouldn't be practical to make these structures without dynamic allocation.

I hope that this has made things clearer for you, rather than more obscure. Comments and corrections welcome.

commented: Very nice! +13

Loud and clear and explained well enough that a five-year-old could understand it. I guess my next question in this line (and I think I have a good idea what the answer is) would be:

Is it considered good practice to allocate memory on the heap if I want to be able to deallocate it before the object goes out of scope, or is it better to redesign my scope altogether so that the object is contained only so long as it is needed, returning necessary values? This is what I'm looking at right now:

/*The Configuration class has a self-contained menu subroutine for editing the application settings that is called from the main menu. The Configuration instance is allocated on the heap and its constructor initializes the settings. Once an option is chosen on the main menu doesn't involve creation/modification of settings managed by the Configuration class, relevant data is pulled from the Configuration instance, and it's then deleted before moving on.*/
void home_menu()
{
    Configuration *cfg = new Configuration;

    string main_opt;
    bool exit_home = false;
    do
    {
        cout << "[S]can, [O]ptions, E[x]it" << endl;
        getline(cin, main_opt);
        if (main_opt.size() == 1)
        {
            transform(main_opt.begin(), main_opt.end(), main_opt.begin(), ::tolower);
            if (main_opt.compare("o") == 0)
                cfg->options_menu();
            else
            {
                params_struct params = cfg->get_params();
                delete cfg;
                const params_struct &rParams = params;
                if (main_opt.compare("x") == 0)
                    exit_home = true;
                else if (main_opt.compare("s") == 0)
                    cout << "Let's scan!";
                else
                    cout << "Invalid selection." << endl;
            }
        }
        else
            cout << "Invalid selection.";
    } while (exit_home == false);
}

Do I need to completely rethink the way I'm going about this?

Disregard that. I see now that with the way I've got it set up now, I've given myself an opportunity to delete the pointer more than once, resulting in runtime errors.

I'll just say that in the code given in your last post, you certainly don't need to allocate the Configuration object on the heap. In general, there is almost no reason to ever dynamically allocate a single object within a function just to use it in that function (use a local object instead). And by the way, a little correction on Schol-R-LEA, the more formal term for dynamic memory or heap-based memory is the term "freestore", the other two terms are colloquial terms.

When it comes down to it, there are really only three main reasons to use dynamic allocation of memory:

  1. If you have to store a number of objects (e.g., array) for which the size (or number of objects) is unknown at compile-time, e.g., a dynamic array (or other dynamically-sized data structures, like strings, trees or lists).
  2. When using polymorphic objects which often have to be heap-based (mostly due to how they are produced and then owned).
  3. When you have objects that are meant to be shared amongst different parts of the code, and the life-time of those objects can vary depending on where they are still in use (e.g., there is no single and obvious owner for it).

And, in modern C++ programming, none of these cases involve using the new/delete mechanism directly, nor (raw-)pointers for that matter. And for those reasons, delete is a very rarely used keyword in day-to-day C++ programming, but it is important to understand it (new can also be almost entirely avoided, but it is still common anyways). So, take the time you need to understand new/delete mechanisms, but also know that there are much better and more practical solutions, especially STL containers and smart-pointers.

Indeed.
@ line 20

delete cfg;

will delete the 1st time when it gets there, but there can be multiple times when that line is accessed, so in further accesses it will crash the program, not knowing what to delete.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.