Calling an object's destructor in its assignment operator method - c++

In my assignment operator method I first destroy any resources that the object manages, and then assign, so:
struct Animal
{
int aNumber;
int * buffer;
Animal() { buffer = new int[128]; }
Animal& operator= (Animal& other)
{
if (this != &other){
delete [] buffer;
//this->~Animal(); // I'm wondering if I can call this instead of deleting buffer here.
aNumber = other.aNumber;
}
~Animal() { delete[] buffer;}
};
The reason I'm asking this is so that instead of rewriting the deleting code, I can just have it in one place. Also, I don't think that calling the destructor deallocates the memory, so when I assign aNumber after calling the destructor, I think it's OK. When I say the memory isn't deallocated I mean for example if I had a vector<Animal>, and the vector called the copy assignment operator for vector[0], vector[0] Animal would call its own destructor and then assign aNumber, but the memory is managed by vector (it's not deallocated). Am I right that the memory isn't deallocated?

After a destructor call the region of memory that held the object is just raw memory.
You can't use the result of just assigning to apparent members there.
It needs a constructor call to re-establish an object there.
But don't do this.
It's fraught with dangers, absolutely hostile deadly territory, and besides it's smelly and unclean.
Instead of
int* buffer;
Animal() { buffer = new int[128]; }
do
vector<int> buffer;
and expand that buffer as necessary as you add items to it, e.g. via push_back.
A std::vector automates the memory management for you, and does it guaranteed correctly. No bugs. Much easier.
In other news, the signature
Animal& operator= (Animal& other)
only lets you assign from non-const Animal objects specified with lvalue expressions (i.e. not temporaries), because only those can be bound the formal argument's reference to non-const.
One way to fix that is to add a const:
Animal& operator= (Animal const& other)
which communicates the intention to not modify the actual argument.

I'm wondering if I can call [the destructor] instead of deleting buffer here.
You may not.
so when I assign aNumber after calling the destructor, I think it's OK
It is not OK. An explicit destructor call ends the lifetime of the object. You may not access members of an object after its lifetime has ended. The behaviour is undefined.
Am I right that the memory isn't deallocated?
You are right, but that doesn't matter.
The reason I'm asking this is so that instead of rewriting the deleting code, I can just have it in one place.
That can be achieved by writing a function that frees the resources, and call that function from both the assignment operator and the destructor.

Related

Do constructor object assignments leak memory

Say I have a simple class like this:
class MyObj
{
char* myPtr;
public:
MyObj()
{
myPtr = malloc(30);
}
~MyObj()
{
free(myPtr);
}
}
class TestObject
{
MyObj _myObj;
public:
TestObject(MyObj myObj)
{
_myObj = myObj;
}
};
Does this leak memory? My reasoning is that there is already an instance of MyObj contained in the TestObject by the time the constructor runs, so doesn't that blow away the myPtr before the memory can be freed? Does assigning to a local object call the destructor of the object being replaced? Does the compiler optimize away the assignment of an object instance variable if it is directly assigned in the constructor? I'm coming from C# where an object doesn't get automatically initialized just by declaring a reference type variable, so this is kind of confusing me.
Thanks!
Does this leak memory?
Yes. The assignment of myObj will invoke the default copy-assignment operator, as no override was provided by you. As a result, a member-by-member copy is performed, and the myPtr instance of the assignment target is overwritten with the myPtr instance from the assignment source. There introduces two problems, frequently encountered when violating one or more parts of the Rule of Three/Five/Zero:
You lose the original myPtr content from the target of the assignment. Thus, the original memory uniquely referred to by that pointer is leaked.
You now share the same pointer value in two myPtr members: both the source and the target of the assignment operation.
The latter is especially troubling, as myObj is leaving scope immediately after the assignment is complete within the TestObject constructor. In doing so, myObj will be destroyed, and with that, it's myPtr freed. Further, myObj was passed in to that constructor by value, not reference, so an implicit copy is already likely to have happened (short of elided copy due to rvalue move semantics). Therefore, three MyObj objects may well be hoisting a myPtr that all reference the same memory, and as soon as one releases it, the rest are unknowingly hoisting dangling pointers. Any dereference or free-ing of those pointers will invoke undefined behavior.
Does assigning to a local object call the destructor of the object being replaced?
Destructors are only invoked to live to their namesake. I.e., they're only invoked when an object is being destroyed (manual invoke of destructors for placement-new semantics notwithstanding). Copy-assignment doesn't do that unless temporaries are introduced, and that isn't the case in your code.
Does the compiler optimize away the assignment of an object instance variable if it is directly assigned in the constructor?
No, but a member initialization list can assist in that regard.
Modern C++ programming techniques frequently use RAII to accomplish what you seem to be trying in a number of ways, depending on the goal you're really trying to achieve.
Unique Data Per Instance
If the goal is unique dynamic data per instance, you can accomplish this easily with either std::vector<char>, or simply std::string, depending on the underlying need. Both are RAII data types and are usually ample for dynamic memory management needs.
class MyObj
{
std::vector<char> myData;
public:
MyObj() : myData(30)
{
}
}
class TestObject
{
MyObj _myObj;
public:
TestObject(MyObj myObj)
: _myObj(std::move(myObj))
{
}
};
This eliminates the need to a destructor in MyObj, and utilizes move semantics as well as the aforementioned member initialization list in the TestObject constructor. All instances of MyObj will hoist a distinct vector of char. All assignment operations for MyObj and TestObject work with default implementations.
Assignments Share Memory
Unlikely you desire this, but it is none-the-less feasible:
class MyObj
{
std::shared_ptr<char> myPtr;
public:
MyObj() : myPtr(new char[30])
{
}
};
class TestObject
{
MyObj _myObj;
public:
TestObject(MyObj myObj)
: _myObj(std::move(myObj))
{
}
};
Similar code, but different member type. Now myPtr is a shared pointer to an array of char. Any assignment to a different myPtr joins the share list. In short, assignment means both object reference the same data, and reference-counting ensures the last-man-standing sweeps up the mess.
Note: There is the possibility of a memory leak using shared pointers like this, as the new may succeed, but the shared data block of the shared-pointer may throw an exception. This is addressed in C++17,
where std::make_shared supports array-allocation
These are just a few ways of doing what you may be trying to accomplish. I encourage you to read about the Rule of Three/Five/Zero and about RAII concepts both at the links provided and on this site. There are plenty of examples that will likely answer further questions you may have.

Caveats and risks of calling a constructor and destructor like common methods?

There's a point in my program where the state of a certain object needs to be reset "to factory defaults". The task boils down to doing everything that is written in the destructor and constructor. I could delete and recreate the object - but can I instead just call the destructor and the constructor as normal objects? (in particular, I don't want to redistribute the updated pointer to the new instance as it lingers in copies in other places of the program).
MyClass {
public:
MyClass();
~MyClass();
...
}
void reinit(MyClass* instance)
{
instance->~MyClass();
instance->MyClass();
}
Can I do this? If so, are there any risks, caveats, things I need to remember?
If your assignment operator and constructor are written correctly, you should be able to implement this as:
void reinit(MyClass* instance)
{
*instance = MyClass();
}
If your assignment operator and constructor are not written correctly, fix them.
The caveat of implementing the re-initialisation as destruction followed by construction is that if the constructor fails and throws an exception, the object will be destructed twice without being constructed again between the first and second destruction (once by your manual destruction, and once by the automatic destruction that occurs when its owner goes out of scope). This has undefined behaviour.
You could use placement-new:
void reinit(MyClass* instance)
{
instance->~MyClass();
new(instance) MyClass();
}
All pointers remain valid.
Or as a member function:
void MyClass::reinit()
{
~MyClass();
new(this) MyClass();
}
This should be used carefully, see http://www.gotw.ca/gotw/023.htm, which is about implementing an assignement operator with this trick, but some points apply here too:
The constructor should not throw
MyClass should not be used as a base class
It interferes with RAII, (but this could be wanted)
Credit to Fred Larson.
Can I do this? If so, are there any risks, caveats, things I need to remember?
No you can't do this. Besides it's technically possible for the destructor call, it will be just undefined behavior.
Supposed you have implemented the assignment operator of your class correctly, you could just write:
void reinit(MyClass* instance) {
*instance = MyClass();
}
You should use a smart pointer and rely on move semantics to get the behavior you want.
auto classObj = std::make_unique<MyClass>();
This creates a wrapped pointer that handles the dynamic memory for you. Suppose you are ready to reset classObj to the factory defaults, all you need is:
classObj = std::make_unique<MyClass>();
This "move-assignment" operation will call the destructor of MyClass, and then reassign the classObj smart pointer to point to a newly constructed instance of MyClass. Lather, rinse, repeat as necessary. In other words, you don't need a reinit function. Then when classObj is destroyed, its memory is cleaned up.
instance->MyClass(); is illegal, you must get a compilation error.
instance->~MyClass(); is possible. This does one of two things:
Nothing, if MyClass has a trivial destructor
Runs the code in the destructor and ends the lifetime of the object, otherwise.
If you use an object after its lifetime is ended, you cause undefined behaviour.
It is rare to write instance->~MyClass(); unless you either created the object with placement new in the first place, or you are about to re-create the object with placement new.
In case you are unaware, placement new creates an object when you already have got storage allocated. For example this is legal:
{
std::string s("hello");
s.~basic_string();
new(&s) std::string("goodbye");
std::cout << s << '\n';
}
You can try using placement new expression
new (&instance) MyClass()

dynamically-sized text object with a copy constructor, a trivial assignment operator, and a trivial destructor

I've been shown that a std::string cannot be inserted into a boost::lockfree::queue.
boost::lockfree::queue is too valuable to abandon, so I think I could use very large, fixed length chars to pass the data according to the requirements (assuming that even satifies since I'm having trouble learning about how to satisfy these requirements), but that will eat up memory if I want large messages.
Does a dynamically-sized text object with a copy constructor, a trivial assignment operator, and a trivial destructor exist? If so, where? If not, please outline how to manifest one.
A dynamically-size type with a trivial copy ctor/dtor is not possible. There are two solutions to your problem, use a fixed sized type, or store pointers in the queue:
boost::lockfree::queue<std::string*> queue(some_size);
// push on via new
queue.push(new std::string("blah"));
// pop and delete
std::string* ptr;
if(queue.pop(ptr))
{
delete ptr;
}
Does a dynamically-sized text object with a copy constructor, a trivial assignment operator, and a trivial destructor exist?
Dynamically sized, no. For something to have a trivial destructor, it requires that the destructor of the object is implicit (or defaulted), and any non-static member objects also have implicit (or defaulted) destructors. Since anything that is dynamically allocated will require a delete [] somewhere along the line in a destructor, you cannot have this constraint satisfied.
To expand upon the above, consider a (very cut down) example of what happens in std::string:
namespace std
{
// Ignoring templates and std::basic_string for simplicity
class string
{
private:
char* internal_;
// Other fields
public:
string(const char* str)
: internal_(new char[strlen(str) + 1])
{ }
};
}
Consider what would happen if we left the destructor as default: it would destroy the stack-allocated char * (that is, the pointer itself, not what it points to). This would cause a memory leak, as we now have allocated space that has no references and hence can never be freed. So we need to declare a destructor:
~string()
{
delete[] internal_;
}
However, by doing this, the destructor becomes user-defined and is therefore non-trivial.
This will be a problem with anything that is dynamically allocated. Note that we cannot fix this by using something like a shared_ptr or a vector<char> as a member variable; even though they may be stack allocated in our class, underneath, they are simply taking care of the memory management for us: somewhere along the line with these, there is a new [] and corresponding delete [], hence they will have non-trivial destructors.
To satisfy this, you'll need to use a stack allocated char array. That means no dynamic allocation, and therefore a fixed size.

Memory management of vector

I have a C++ class with a private "pointer to vector" member pV, I assign a new vector to it in the constructor...
pV = new vector<FMCounter>(n, FMCounter(arg1))>;
However when I delete in the destructor of the class
delete pV;
I get a segfault and a message that I'm trying to free pv that wasn't allocated in the first place. I checked that pV->size() was 4K something, so I am sure it was allocated memory by new.
Pointer members with ownership semantics (allocating in constructor and deallocating in destructor) usually require to write a custom copy constructor and assignment operator (usually known as the Rule of Three), as the compiler generated ones will just copy the pointer member and not its underlying object. So if you at some point copy your containing object, you end up with two objects having the same pointer as member and the one destroyed secondly tries to delete an already deleted pointer.
At the simplest you should make sure your copy constructor does something like
TheClass::TheClass(const TheClass &rhs)
: pV(new vector<FMCounter>(*rhs.pV))
{
}
and your assigment operator does something like
TheClass& TheClass::operator=(const TheClass &rhs)
{
*pV = *rhs.pV
return *this;
}

explicit destructor

The following code is just used to illustrate my question.
template<class T>
class array<T>
{
public:
// constructor
array(cap = 10):capacity(cap)
{element = new T [capacity]; size =0;}
// destructor
~array(){delete [] element;}
void erase(int i);
private:
T *element;
int capacity;
int size;
};
template<class T>
void class array<T>::erase(int i){
// copy
// destruct object
element[i].~T(); ////
// other codes
}
If I have array<string> arr in main.cpp. When I use erase(5), the object of element[5] is destroyed but the space of element[5] will not be deallocated, can I just use element[5] = "abc" to put a new value here? or should I have to use placement new to put new value in the space of element [5]?
When program ends, the arr<string> will call its own destructor which also calls delete [] element. So the destructor of string will run to destroy the object first and then free the space. But since I have explicitly destruct the element[5], does that matter the destructor (which is called by the arr's destuctor) run twice to destruct element[5]? I know the space can not be deallocated twice, how about the object? I made some tests and found it seems fine if I just destruct object twice instead of deallocating space twice.
Update
The answers areļ¼š
(1)I have to use placement new if I explicitly call destructor.
(2) repeatedly destructing object is defined as undefined behavior which may be accepted in most systems but should try to avoid this practice.
You need to use the placement-new syntax:
new (element + 5) string("abc");
It would be incorrect to say element[5] = "abc"; this would invoke operator= on element[5], which is not a valid object, yielding undefined behavior.
When program ends, the arr<string> will call its own destructor which also calls delete [] element.
This is wrong: you are going to end up calling the destructor for objects whose destructors have already been called (e.g., elements[5] in the aforementioned example). This also yields undefined behavior.
Consider using the std::allocator and its interface. It allows you easily to separate allocation from construction. It is used by the C++ Standard Library containers.
Just to explain further exactly how the UD is likely to bite you....
If you erase() an element - explicitly invoking the destructor - that destructor may do things like decrement reference counters + cleanup, delete pointers etc.. When your array<> destructor then does a delete[] element, that will invoke the destructors on each element in turn, and for erased elements those destructors are likely to repeat their reference count maintenance, pointer deletion etc., but this time the initial state isn't as they expect and their actions are likely to crash the program.
For that reason, as Ben says in his comment on James' answer, you absolutely must have replaced an erased element - using placement new - before the array's destructor is invoked, so the destructor will have some legitimate state from which to destruct.
The simplest type of T that illustrates this problem is:
struct T
{
T() : p_(new int) { }
~T() { delete p_; }
int* p_;
};
Here, the p_ value set by new would be deleted during an erase(), and if unchanged when ~array() runs. To fix this, p_ must be changed to something for which delete is valid before ~array() - either by somehow clearing it to 0 or to another pointer returned by new. The most sensible way to do that is by the placement new, which will construct a new object, obtaining a new and valid value for p_, overwriting the old and useless memory content.
That said, you might think you could construct types for which repeated destructor was safe: for example, by setting p_ to 0 after the delete. That would probably work on most systems, but I'm pretty sure there's something in the Standard saying to invoke the destructor twice is UD regardless.