C++ Object Created with New, Destroyed with Free(); How Bad Is This

C++ object created with new, destroyed with free(); How bad is this?

Yes it does matter.

For memory obtained using new you must use delete.

For memory obtained using malloc you must use free.

new and malloc may use different data structures internally to keep track of what and where it has allocated memory. So in order to free memory, you have to call that corresponding function that knows about those data structures. It is however generally a bad idea to mix these two types of memory allocation in a piece of code.

c++ malloc and free object creation and deletion

Reading so many points from so many I myself wrote a program to explain your problem. See below :-

#include <iostream>
#include <vector>
using namespace std;

class X
{
int x;
vector<int> v;
public:
X()
{
cout<<"constructing\n";
x=0;
v.push_back(1);
v.push_back(2);
}
int& getx()
{
return x;
}
vector<int>& getv()
{
return v;
}
~X()
{
cout<<"destroying\n";
}
};
int main()
{
X* p=(X*)operator new(sizeof(X));
++p->getx();
p->getx()*=5;
cout<<p->getx()<<"\n";
for (int x:p->getv())
cout<<x<<" ";
cout<<"\nexecuted\n";
operator delete(p);
return 0;
}
/* Output :-
5

executed
*/

See how p ignored the vector v and went for the line executed. This is because vector<int> being a class (or more precisely a class template) was never created by operator new (or malloc in your case). Your program showed the output for x because it is a primitive type & not a class. For a class you need a constructor & hence operator new or malloc aren't suitable for classes & hence the output. If you simply replace operator new with new & operator delete with delete then the output will be :-

constructing
5
1 2
executed
destroying

Now your code gives the correct & apt results ! Congo !

For your second question, NEVER MIX UP malloc & free with new & delete as it creates UB with not so happening results.

Object destruction in C++

In the following text, I will distinguish between scoped objects, whose time of destruction is statically determined by their enclosing scope (functions, blocks, classes, expressions), and dynamic objects, whose exact time of destruction is generally not known until runtime.

While the destruction semantics of class objects are determined by destructors, the destruction of a scalar object is always a no-op. Specifically, destructing a pointer variable does not destroy the pointee.

Scoped objects

automatic objects

Automatic objects (commonly referred to as "local variables") are destructed, in reverse order of their definition, when control flow leaves the scope of their definition:

void some_function()
{
Foo a;
Foo b;
if (some_condition)
{
Foo y;
Foo z;
} <--- z and y are destructed here
} <--- b and a are destructed here

If an exception is thrown during the execution of a function, all previously constructed automatic objects are destructed before the exception is propagated to the caller. This process is called stack unwinding. During stack unwinding, no further exceptions may leave the destructors of the aforementioned previously constructed automatic objects. Otherwise, the function std::terminate is called.

This leads to one of the most important guidelines in C++:

Destructors should never throw.

non-local static objects

Static objects defined at namespace scope (commonly referred to as "global variables") and static data members are destructed, in reverse order of their definition, after the execution of main:

struct X
{
static Foo x; // this is only a *declaration*, not a *definition*
};

Foo a;
Foo b;

int main()
{
} <--- y, x, b and a are destructed here

Foo X::x; // this is the respective definition
Foo y;

Note that the relative order of construction (and destruction) of static objects defined in different translation units is undefined.

If an exception leaves the destructor of a static object, the function std::terminate is called.

local static objects

Static objects defined inside functions are constructed when (and if) control flow passes through their definition for the first time.1
They are destructed in reverse order after the execution of main:

Foo& get_some_Foo()
{
static Foo x;
return x;
}

Bar& get_some_Bar()
{
static Bar y;
return y;
}

int main()
{
get_some_Bar().do_something(); // note that get_some_Bar is called *first*
get_some_Foo().do_something();
} <--- x and y are destructed here // hence y is destructed *last*

If an exception leaves the destructor of a static object, the function std::terminate is called.

1: This is an extremely simplified model. The initialization details of static objects are actually much more complicated.

base class subobjects and member subobjects

When control flow leaves the destructor body of an object, its member subobjects (also known as its "data members") are destructed in reverse order of their definition. After that, its base class subobjects are destructed in reverse order of the base-specifier-list:

class Foo : Bar, Baz
{
Quux x;
Quux y;

public:

~Foo()
{
} <--- y and x are destructed here,
}; followed by the Baz and Bar base class subobjects

If an exception is thrown during the construction of one of Foo's subobjects, then all its previously constructed subobjects will be destructed before the exception is propagated. The Foo destructor, on the other hand, will not be executed, since the Foo object was never fully constructed.

Note that the destructor body is not responsible for destructing the data members themselves. You only need to write a destructor if a data member is a handle to a resource that needs to be released when the object is destructed (such as a file, a socket, a database connection, a mutex, or heap memory).

array elements

Array elements are destructed in descending order. If an exception is thrown during the construction of the n-th element, the elements n-1 to 0 are destructed before the exception is propagated.

temporary objects

A temporary object is constructed when a prvalue expression of class type is evaluated. The most prominent example of a prvalue expression is the call of a function that returns an object by value, such as T operator+(const T&, const T&). Under normal circumstances, the temporary object is destructed when the full-expression that lexically contains the prvalue is completely evaluated:

__________________________ full-expression
___________ subexpression
_______ subexpression
some_function(a + " " + b);
^ both temporary objects are destructed here

The above function call some_function(a + " " + b) is a full-expression because it is not part of a larger expression (instead, it is part of an expression-statement). Hence, all temporary objects that are constructed during the evaluation of the subexpressions will be destructed at the semicolon. There are two such temporary objects: the first is constructed during the first addition, and the second is constructed during the second addition. The second temporary object will be destructed before the first.

If an exception is thrown during the second addition, the first temporary object will be destructed properly before propagating the exception.

If a local reference is initialized with a prvalue expression, the lifetime of the temporary object is extended to the scope of the local reference, so you won't get a dangling reference:

{
const Foo& r = a + " " + b;
^ first temporary (a + " ") is destructed here
// ...
} <--- second temporary (a + " " + b) is destructed not until here

If a prvalue expression of non-class type is evaluated, the result is a value, not a temporary object. However, a temporary object will be constructed if the prvalue is used to initialize a reference:

const int& r = i + j;

Dynamic objects and arrays

In the following section, destroy X means "first destruct X and then release the underlying memory".
Similarly, create X means "first allocate enough memory and then construct X there".

dynamic objects

A dynamic object created via p = new Foo is destroyed via delete p. If you forget to delete p, you have a resource leak. You should never attempt to do one of the following, since they all lead to undefined behavior:

  • destroy a dynamic object via delete[] (note the square brackets), free or any other means
  • destroy a dynamic object multiple times
  • access a dynamic object after it has been destroyed

If an exception is thrown during the construction of a dynamic object, the underlying memory is released before the exception is propagated.
(The destructor will not be executed prior to memory release, because the object was never fully constructed.)

dynamic arrays

A dynamic array created via p = new Foo[n] is destroyed via delete[] p (note the square brackets). If you forget to delete[] p, you have a resource leak. You should never attempt to do one of the following, since they all lead to undefined behavior:

  • destroy a dynamic array via delete, free or any other means
  • destroy a dynamic array multiple times
  • access a dynamic array after it has been destroyed

If an exception is thrown during the construction of the n-th element, the elements n-1 to 0 are destructed in descending order, the underlying memory is released, and the exception is propagated.

(You should generally prefer std::vector<Foo> over Foo* for dynamic arrays. It makes writing correct and robust code much easier.)

reference-counting smart pointers

A dynamic object managed by several std::shared_ptr<Foo> objects is destroyed during the destruction of the last std::shared_ptr<Foo> object involved in sharing that dynamic object.

(You should generally prefer std::shared_ptr<Foo> over Foo* for shared objects. It makes writing correct and robust code much easier.)

Why does the use of 'new' cause memory leaks?

What is happening

When you write T t; you're creating an object of type T with automatic storage duration. It will get cleaned up automatically when it goes out of scope.

When you write new T() you're creating an object of type T with dynamic storage duration. It won't get cleaned up automatically.

new without cleanup

You need to pass a pointer to it to delete in order to clean it up:

newing with delete

However, your second example is worse: you're dereferencing the pointer, and making a copy of the object. This way you lose the pointer to the object created with new, so you can never delete it even if you wanted!

newing with deref

What you should do

You should prefer automatic storage duration. Need a new object, just write:

A a; // a new object of type A
B b; // a new object of type B

If you do need dynamic storage duration, store the pointer to the allocated object in an automatic storage duration object that deletes it automatically.

template <typename T>
class automatic_pointer {
public:
automatic_pointer(T* pointer) : pointer(pointer) {}

// destructor: gets called upon cleanup
// in this case, we want to use delete
~automatic_pointer() { delete pointer; }

// emulate pointers!
// with this we can write *p
T& operator*() const { return *pointer; }
// and with this we can write p->f()
T* operator->() const { return pointer; }

private:
T* pointer;

// for this example, I'll just forbid copies
// a smarter class could deal with this some other way
automatic_pointer(automatic_pointer const&);
automatic_pointer& operator=(automatic_pointer const&);
};

automatic_pointer<A> a(new A()); // acts like a pointer, but deletes automatically
automatic_pointer<B> b(new B()); // acts like a pointer, but deletes automatically

newing with automatic_pointer

This is a common idiom that goes by the not-very-descriptive name RAII (Resource Acquisition Is Initialization). When you acquire a resource that needs cleanup, you stick it in an object of automatic storage duration so you don't need to worry about cleaning it up. This applies to any resource, be it memory, open files, network connections, or whatever you fancy.

This automatic_pointer thing already exists in various forms, I've just provided it to give an example. A very similar class exists in the standard library called std::unique_ptr.

There's also an old one (pre-C++11) named auto_ptr but it's now deprecated because it has a strange copying behaviour.

And then there are some even smarter examples, like std::shared_ptr, that allows multiple pointers to the same object and only cleans it up when the last pointer is destroyed.

Revive object from destructor in C++?

The short answer is: no. C++ does not employ garbage collection, like Java or C#. When an object is destroyed, it's destroyed immediately. Gone for good. Joined the choir invisible. Pining for the fjords, etc...

And to say this over a couple of times in different words so that there is no possible weasily reinterpretation...

The destructor is invoked as part of object destruction. Object destruction consists of invoking the destructor and deallocating the memory that was used for the object itself. It's a single process, not two separate processes. While the destructor is running, the object still exists, for the destructor to use, but it exists on borrowed time. It's a foregone conclusion that the object is going to be vaporized as soon as the destructor returns. Once a destructor is invoked, the object is going to be destroyed, and nothing is going to change its fate.

Understand this: the reason why a destructor is being invoked is because either: the object was originally allocated on the heap with "new", and it's now being "delete"d. "delete" means "delete", not "delete maybe". So the object's getting deleted. Or, if the object was allocated on the stack, the execution thread exited the scope, so all objects declared in the scope are getting destroyed. The destructor is, technically, getting invoked as a result of the object being destroyed. So, the object is being destroyed. The End.

Having said that, C++ allows you to implement a custom allocator for your classes. If you feel like it, you can write your own custom memory allocation and deallocation functions that implement whatever functionality you want. Though these are never used for stack allocated objects (i.e. local variables).

What is the use of creating objects on the free store?

In your example, there is none, and it isn't good practice to
use dynamic allocation. Dynamic allocation is used when objects
have identity (or cannot be copied for some other reason), and
the lifetime of the object does not correspond to some
pre-determined lifetime, like static or auto. Dynamic
allocation may also be used in a few cases where copying is
expensive, and the profiler shows that the copying is
a bottleneck; in such cases, using dynamic allocation and
copying a pointer may remove the bottleneck. (But this should
never be done until profiling shows it necessary.)

Do I need to destroy each instance of new class?

Destructors are called automatically and you usually don't have to worry about it unless you're allocating memory dynamically with new.

In that case, you will have to call delete for each allocated memory once you don't need it any more.

Note that if you're allocating an array with new [], you will have to use delete[] for desallocation:

int *a = new int[10]; // allocating an array of 10 integers

//...

delete[] a; // release memory

In modern C++, you should consider managed pointers which will do the job for you. Something like:

while(read >> f >> l >> t >> s >> sta >> acct >> bal)
{
cout << "test" << endl;
ptr[i] = std::make_unique<account>(f,l,t,s,sta,acct,bal);
ptr[i]->printcontents();
cout << "test" << endl;
i++;
cout << i << endl;
}

Here, std::make_unique will return a std::unique_ptr which will call delete on the associated memory when destroyed.


Last point : are you sure you really need pointers? Hard to say from your example, but depending on your usage you may as well create statically allocated objects:

while(read >> f >> l >> t >> s >> sta >> acct >> bal)
{
cout << "test" << endl;
ptr[i] = account(f,l,t,s,sta,acct,bal);
ptr[i].printcontents();
cout << "test" << endl;
i++;
cout << i << endl;
}


Related Topics



Leave a reply



Submit