new and delete in c++

195 Views Asked by At

im doing an exercise and I cant understand if i'm creating garbage with this program. As far as I understand there shouldn't be any garbage left and I should not use delete in this case, but it makes me wonder because in class the teacher said that every new has to have a delete somewhere.

#include <iostream>

using namespace std;

bool check_nr_in_array(int number, int* array_of_int_pointers[], int index);
int find_index(int number, int* array_of_int_pointers, int index);
bool mod_array_of_pointers(int number, bool presence, int* array_of_int_pointers[], int index);

int main()
{
    int* A[1000];
    int x;
    int cursor = 0;
    int alloc_var = 0;
    bool state = false;

    do
    {
        cout << "Insert a number: ";
        cin >> x;
        if(x >= 0)
        {
            state = mod_array_of_pointers(x, check_nr_in_array(x, A, cursor), A, cursor);
            if(state)
                alloc_var++;
            cursor++;
        }
    }while(x >= 0);

    cout << "Allocated" << alloc_var << " variables." << endl;
    for(int i = 0; i < cursor; i++)
        cout << *(A[i]) << " ";

    return 0;
}

bool check_nr_in_array(int number, int* array_of_int_pointers[], int index)
{
    for(int i = 0; i < index; i++)
    {
        if(*array_of_int_pointers[i] == number)
            return true;
    }
    return false;
}

int find_index(int number, int* array_of_int_pointers[], int index)
{
    for(int i = 0; i < index; i++)
    {
        if(*array_of_int_pointers[i] == number)
            return i;
    }
    return 0;
}
bool mod_array_of_pointers(int number, bool presence, int* array_of_int_pointers[], int index)
{
    if(!presence)
    {
        array_of_int_pointers[index] = new int;
        *array_of_int_pointers[index] = number;
        return true;
    }else{
        array_of_int_pointers[index] = array_of_int_pointers[find_index(number, array_of_int_pointers, index)];
        return false;
    }
}

The program works but i dont know how to check if im creating garbage somewhere. the exercise itself is just creating an array of int pointers, and you ask for a number until the number is negative. If the inserted nr is already somewhere in the array u should copy it's address otherwise you need to allocate another var with new.

fixed the code like below, can somebody check and let me know if it's still leaking ?

#include <iostream>

using namespace std;

bool check_nr_in_array(int number, int* array_of_int_pointers[], int index);
int find_index(int number, int* array_of_int_pointers, int index);
bool mod_array_of_pointers(int number, bool presence, int* array_of_int_pointers[], int index, bool checksum[]);
void deallocate(int* array_of_int_pointers[], bool checksum[], int index);
int main()
{
    int* A[1000];
    int x;
    int cursor = 0;
    int alloc_var = 0;
    bool state = false;
    bool checksum[1000];

    do
    {
        cout << "Inserire un numero: ";
        cin >> x;
        if(x >= 0)
        {
            state = mod_array_of_pointers(x, check_nr_in_array(x, A, cursor), A, cursor, checksum);
            if(state)
                alloc_var++;
            cursor++;
        }
    }while(x >= 0);

    cout << "Allocate " << alloc_var << " variabili." << endl;
    for(int i = 0; i < cursor; i++)
        cout << *(A[i]) << " ";

    deallocate(A, checksum, cursor);

    return 0;
}

bool check_nr_in_array(int number, int* array_of_int_pointers[], int index)
{
    for(int i = 0; i < index; i++)
    {
        if(*array_of_int_pointers[i] == number)
            return true;
    }
    return false;
}

int find_index(int number, int* array_of_int_pointers[], int index)
{
    for(int i = 0; i < index; i++)
    {
        if(*array_of_int_pointers[i] == number)
            return i;
    }
    return 0;
}
bool mod_array_of_pointers(int number, bool presence, int* array_of_int_pointers[], int index, bool checksum[])
{
    if(!presence)
    {
        array_of_int_pointers[index] = new int;
        *array_of_int_pointers[index] = number;
        checksum[index] = true;
        return true;
    }else{
        array_of_int_pointers[index] = array_of_int_pointers[find_index(number, array_of_int_pointers, index)];
        checksum[index] = false;
        return false;
    }
}

void deallocate(int* array_of_int_pointers[], bool checksum[], int index)
{
    for(int i = 0; i < index; i++)
        if(checksum[i] == 1)
            delete array_of_int_pointers[i];
}

1

There are 1 best solutions below

0
tbxfreeware On

Use a profiler

I cant understand if I'm creating garbage with this program. The program works but I don't know how to check if I'm creating garbage somewhere.

The C++ language does not have any commands or functions to tell you if a program leaks memory. For that, you have to use tools like Valgrind to profile your memory usage.

Here is the way Wikipedia begins its article about Valgrind:

Valgrind is a programming tool for memory debugging, memory leak detection, and profiling.

You can read the rest, if you want to investigate further. Professional programmers use tools like Valgrind. Students, however, are not expected to master third-party tools at a time when they are still learning C++ itself.

Every new needs a corresponding delete

In class the teacher said that every new has to have a delete somewhere.

Your teacher is right!

Best practice is to accomplish that by creating an RAII class to manage an object that uses dynamically allocated memory. The constructor calls operator new to allocate the object, and the destructor calls delete to deallocate it.

RAII is an acronym that stands for "resource aquisition is initialization," horrible name that was invented by no less than Bjarne Stroustrup himself. RAII focuses on the idea of ownership. The owner of an object is the one who is responsible for "cleaning up" when that object is deallocated. In the case of dynamically allocated memory, that means calling operator delete. For a class such as std::fstream, which is an RAII class, that means closing the file.

Exception safety is an overriding reason to use RAII classes. They are the only way to guarantee that an RAII object will be properly disposed of when an exception causes the unexpected termination of a program.

At this stage of your journey into C++, I will assume that you have not yet studied classes, so I will not give any source-code examples of an RAII class here.

The most natural solution to this problem would be to use an RAII class named std::unique_ptr. A unique_ptr is a smart pointer that behaves almost the same as regular pointer, except that it automatically calls operator delete on the object that it points to, when it is deleted itself.

Ownership is key. A std::unique_ptr owns the object it points to. So, when the destructor of a std::unique_ptr is invoked, because the pointer itself is being deallocated, it says, in effect, "If I'm getting deleted, I'm taking everything I own with me!"

Array A, in the program below, should be an array of std::unique_ptr objects. If it were, then the dynamically allocated array elements would automatically call delete on the integers they point to. Your program would not need to call operator delete at all. And if you also used a function called std::make_unique, which—trust me—you would, then your program would not need to call operator new, either.

With few exceptions, professionals do not use using namespace std;

There are drawbacks to using namespace std;. See Why is "using namespace std;" considered bad practice?. Your textbook probably should not be teaching you to use it.

Instead, pros just type std:: every time they need to reference a name from the Standard Library. That is why I have used std::cout, std::cin, and so on, in this answer. (There are a few exceptions to this, mostly centered around something called argument-dependent lookup, but we won't go into them here.)

Unless your instructor gets mad, you should do the same thing. And delete using namespace std; from your program!

Defining the assignment

Although the OP does not explicity describe the assignment, from reading the code, I was able to determine that is was this:

Write a program that inputs integers from the user, and adds them to an array that uses dynamically allocated elements. The values entered should be non-negative. When the user enters a negative value, that is the signal to quit.

No number should be stored more than once. Each time the user enters a number, you should search the array to see if it is already stored there. If not, add it at the end of the allocated elements. Otherwise, do nothing. Either way, you should loop back, and ask the user to enter the next number.

At the end of the program, display the number of elements that were allocated, followed by a list of them all.

Based on this, a good place to start is with a simple declaration of variables. There are no globals here. All the variables are defined at the beginning of function main.

// main.cpp
#include <cstddef>
#include <iostream>

int main()
{
    std::cout <<
        "ARRAY INSERTER WITH DYNAMICALLY ALLOCATED ARRAY ELEMENTS"
        "\n"
        "\nEnter a negative number when you want to quit."
        "\n"
        "\nOtherwise, any non-negative number will be added to the array."
        "\nEach number is added only once. If you enter the same number "
        "\nmore than once, only the first entry is added to the array."
        "\n"
        "\nAt the end, we'll tell you how many allocations were "
        "\nmade to the array, and display them all."
        "\n\n";
    constexpr const std::size_t capacity{ 1000 };  // maximum size of array `A`
    std::size_t size{};  // allocated size of array `A`
    int* A[capacity]{};  // array `A`
    int n{};             // number to be entered by the user

    // ...

    return 0;
}
// end file: main.cpp

The only new thing here is the initialization of array A. The trailing braces cause each of its elements to be intialized to nullptr. The variables size and n are both initialized to 0.

std::size_t is an alias for an unsigned integer type that is used by the compiler for array sizes and subscripts. It is also the type returned by operator sizeof. In general, you should prefer it over type int, when you use an array.

std::size_t is defined in the header <cstddef>, so that has been included here.

Function get_int – A safe way get input from the keyboard.

I like to call a function every time I ask the user for input. That way, I can validate the user's entries, and ask him to reenter them if they are invalid. You do not want to lock the user into a loop from which there is no exit, but you do want to keep looping until the user either gets it right, or else cancels the input.

That is what function get_int does below. The user can cancel by entering a negative number. Otherwise, garbage (i.e., non-numeric) entries will be rejected.

int get_int()
{
    if (!std::cin.good())
    {
        std::cout << "ERROR: `get_int` called when "
            "`std::cin` was in a failed state.";
        return -1;  // This causes the caller to exit its loop.
    }
    int n;
    for (;;)
    {
        std::cout << "Enter a number: ";
        if (std::cin >> n)
            return n;
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        std::cout << "Invalid entry. Please reenter.\n\n";
    }
}

for (;;) is an infinite loop. Like I said, we're going to keep doing this, until the user get's it right. The only way to exit is by entering a valid integer for n, which then triggers the return statement.

It is important to test every input operation involving a user to see whether it succeeded or failed. That is what if (std::cin >> n) does. When a valid integer is entered, the expression std::cin >> n will be implicitly converted to a bool, with the value true. If input fails, then it will be converted to false, and the input stream will be placed into a "failed" state.

After a failed entry, it is important to call std::cin.clear, followed by std::cin.ignore. That is how you put std::cin back into a "good" state, and clear the garbage entry from the input stream. The call to function ignore discards all the keystrokes up to, and including, the newline character (i.e., "Enter key") at the end of the line.

I posted a fancier version of get_int in this StackOverflow answer. It adds optional range checks for the entered values, and also optionally allows the user to cancel, by pressing Enter on a blank line. It is a little closer to what I use in my own work.

The main loop

One unexpected benefit of writing function get_int is the clean way in which the main loop of the program can now be written. There are two things it tests. First, it checks to see if there is any capacity left in array A, and, second, it checks to see if the user entered a non-negative number. When both answers are "yes," we keep looping.

    while (size < capacity && (n = get_int()) >= 0)
    {
        // If the array does not contain `n`, add it at location 
        // `size`, and afterwards, increment `size`. Otherwise, 
        // if the array already contains `n`, do nothing.
    }
    std::cout.put('\n');

    // Print stats, and output array...

    // Run a loop to deallocate (i.e., `delete`) array elements...

The first check is just size < capacity. Keep looping so long as the allocated size is less than the overall capacity.

The second is a little trickier, and is worthy of explantion: (n = get_int()) >= 0.

On the left, we have (n = get_int()). This is an assignment. Variable n gets the value returned from get_int. What happens next, though, is that the assigned value is returned to the surrounding expression as the "answer" to the assignment. You end up with (n) >= 0. This is exactly the condition under which we want to keep looping. The user entered a non-negative number.

Delete array elements

The question in the OP has now become trivial. The allocated array elements have subscripts ranging from 0 up to (but not including) size. Run a simple loop that invokes operator delete on those elements.

I have omitted much of the detail. That is intentional. This should give you a good start, but I want to let you have some of the fun, too!