Returning an object: value, pointer and reference - c++

I know this has probably been asked and I've looked through other answers, but still I cannot get this completely.
I want to understand the difference between the two following codes:
MyClass getClass(){
return MyClass();
}
and
MyClass* returnClass(){
return new MyClass();
}
Now let's say I call such functions in a main:
MyClass what = getClass();
MyClass* who = returnClass();
If I got this straight, in the first case the object created in the
function scope will have automatic storage, i.e. when you exit the
scope of the function its memory block will be freed. Also, before
freeing such memory, the returned object will be copied into the
"what" variable I created. So there will exist only one copy of
the object. Am I correct?
1a. If I'm correct, why is RVO (Return Value Optimization) needed?
In the second case, the object will be allocated through a dynamic storage, i.e. it will exist even out of the function scope. So I need to use a deleteon it. The function returns a pointer to such object, so there's no copy made this time, and performing delete who will free the previously allocated memory. Am I (hopefully) correct?
Also I understand I can do something like this:
MyClass& getClass(){
return MyClass();
}
and then in main:
MyClass who = getClass();
In this way I'm just telling that "who" is the same object as the one created in the function. Though, now we're out of the function scope and thus that object doesn't necessarily exists anymore. So I think this should be avoided in order to avoid trouble, right? (and the same goes for
MyClass* who = &getClass();
which would create a pointer to the local variable).
Bonus question: I assume that anything said till now is also true when returning vector<T>(say, for example, vector<double>), though I miss some pieces.
I know that a vector is allocated in the stack while the things it contains are in the heap, but using vector<T>::clear() is enough to clear such memory.
Now I want to follow the first procedure (i.e. return a vector by value): when the vector will be copied, also the onjects it contains will be copied; but exiting the function scope destroys the first object. Now I have the original objects that are contained nowhere, since their vector has been destroyed and I have no way of deleting such objects that are still in the heap. Or maybe a clear() is performed automatically?
I know that I may beatray some misunderstandings in these subjects (expecially in the vector part), so I hope you can help me clarify them.

Q1. What happens conceptually is the following: you create an object of type MyClass on the stack in the stack frame of getClass.
You then copy that object into the return value of the function, which is a bit of stack that was allocated before the function call to hold this object.
Then the function returns, the temporary gets cleaned up. You copy the return value into the local variable what. So you have one allocation and two copies.
Most (all?) compilers are smart enough to omit the first copy: the temporary is not used except as return value. However, the copy from the return value into the local variable on the caller side cannot be omitted, because the return value lives on a part of the stack that is freed as soon as the function finishes.
Q1a. Return Value Optimization (RVO) is a special feature, that does allow that final copy to be elided. That is, instead of returning the function result on the stack, it will be allocated straight away in the memory allocated for what, avoiding all copying altogether. Note that, contrary to all other compiler optimizations, RVO can change the behaviour of your program! You could give MyClass a non-default copy constructor, that has side effects, like printing a message to the console or liking a post on Facebook. Normally, the compiler is not allowed to remove such function calls unless it can prove that these side effects are absent. However, the C++ specs contain a special exception for RVO, that says that even if the copy constructor does something non-trivial, it is still allowed to omit the return value copy and reduce the whole thing to a single constructor call.
2. In the second case, the MyClass instance is not allocated on the stack, but on the heap. The result of the new operator is an integer: the address of the object on the heap. This is the only point where you will ever be able to obtain this address (provided you didn't use placement new), so you need to hold onto it: if you lose it, you cannot call delete and you will have created a memory leak.
You assign the result of new to a variable whose type is denoted by MyClass* so that the compiler can do type checking and stuff, but in memory it is just an integer large enough to hold an address on your system (32- or 64-bits). You can check this for yourself by trying to coerce the result to a size_t (which is typedef'd to typically an unsigned int or something larger depending on your architecture) and seeing the conversion succeed.
This integer is returned to the caller by value, i.e. on the stack, just as in example (1). So again,
in principle, there is copying going on, but in this case only copying of a single integer which your CPU is very good at (most of the times it will not even go on the stack but get passed in a register) and not the whole MyClass object (which in general has to go on the stack because it's very large, read: larger than an integer).
3. Yes, you should not do that. Your analysis is correct: as the function finishes, the local object is cleaned up and its address becomes meaningless. The problem is, that it sometimes seems to work. Forgetting about optimizations for the time being, the main reason the way memory works: clearing (zero-ing) memory is quite expensive, so that is hardly ever done. Instead, it is just marked as available again, but it's not overwritten until you make another allocation that needs it. Therefore, even though the object is technically dead, its data may still be in the memory so when you dereference the pointer you may still get the right data back. However, since the memory is technically free, it may be overwritten at any time between right now and at the end of the universe. You have created what C++ calls Undefined Behaviour (UB): it may seem to work right now on your computer, but there's no telling what may happen somewhere else or at another point in time.
Bonus: When you return a vector by value, as you remarked, it is not just destroyed: it is first copied to the return value or - taking RVO into account - into the target variable. There are two options now: (1) The copy creates its own objects on the heap, and modifies its internal pointers accordingly. You now have two proper (deep) copies co-existing temporarily -- then when the temporary object goes out of scope, you are just left with the one valid vector. Or (2): When copying the vector, the new copy takes ownership of all the pointers that the old one holds. This is possible, if you know that the old vector is about to be destroyed: rather than re-allocating all the contents again on the heap, you can just move them to the new vector and leave the old one in a sort of half-dead state -- as soon as the function is done cleaning that stack the old vector is no longer there anyway.
Which of these two options is used, is really irrelevant or rather, an implementation detail: they have the same result and whether the compiler is smart enough to choose (2) should not usually be your concern (though in practice option (2) will always happen: deep copying an object just to destroy the original is just pointless and easily avoided).
As long as you realize that the thing that gets copied is the part on the stack and the ownership of the pointers on the heap gets transferred: no copying happens on the heap and nothing gets cleared.

Here are my answers to your different questions:
1- You are absolutely correct. If I understand the sequentiallity correctly, your code will allocate memory, create your object, copy the variable into the what variable, and get destroyed as out of scope. The same thing happens when you do:
int SomeFunction()
{
return 10;
}
This will create a temporary that holds 10 (so allocate), copy it to the return vairbale, and then destroy the temporary (so deallocate) (Here I'm not sure of the specifics, maybe the compiler can remove some stuff via automatic inlining, constante values, ... but you get the idea). Which brings me to
1a- You need RVO when to limit this allocation, copy, and deallocation part. If your class allocates a lot of data upon construction it is a bad idea to return it directly. You can use move constructor in that case, and reuse the storage space allocated by the temporary for example. Or return a pointer. Which takes all the way down to
2- Returning a pointer works exactly as returning an int from a function. But because pointers are only 4 or 8 bytes long, allocation and deallocation cost a lot less than doing so for a class that's 10 Mb long. And instead of copying the object you copy its adress on the heap (usually less heavy, but copy nonetheless). Do not forget it is not because a pointer represents a memory that its size is 0 byte. So using a pointer requires getting the value from some memory address. Returning a reference and inlining are also good ideas to optimise your code, as you avoid chasing pointer, function calls, etc.
3- I think you are correct there. I'd have to make sure by testing, but if follow my logic you are right.
I hope I answered your questions. And I hope my answers are as correct as can be. But maybe someone more clever than me can correct me :-)
Best.

Related

move semantics with local variables

I was wondering about the following code taken from this example:
Is it possible to std::move local stack variables?
struct MyStruct
{
int iInteger;
string strString;
};
void MyFunc(vector<MyStruct>& vecStructs)
{
MyStruct NewStruct = { 8, "Hello" };
vecStructs.push_back(std::move(NewStruct));
}
int main()
{
vector<MyStruct> vecStructs;
MyFunc(vecStructs);
}
I understand how std::move() works on a basic level, but there's something under the hood that doesn't make sense to me.
If, for example, NewStruct was created on the stack, locally, as in the example. If I used move semantics to save its data from being destroyed before exiting the function scope, thus NewStruct being destroyed won't affect my data, which is great. But isn't this information still placed on the stack? If I were to expand my use of the stack again, why wouldn't this information be in danger of being overridden once the stack grew and wanted to write over where NewStruct data was originally created?
Adding a second example to maybe get my point across in a better manner:
void MyFunc(vector<char*> &vecCharPointers)
{
char* myStr = {'H', 'e', 'l', 'l', 'o'};
vecCharPointers.push_back(std::move(myStr));
}
int main()
{
vector<char*> vecCharPointers;
char* cp = nullptr;
}
I think your misunderstanding lies in how your data is actually stored. The struct object will be on the stack, containing an int and a std::string object, yes. However, the actual string contents are stored on the free store, not on the stack (ignoring an optimization that isn't important here).
Now let's remove std::move and move semantics from the picture. What would happen? The vector will create a new element on the end that is copy-constructed from your stack variable. This means that the new object will have a copy of the int value and a copy of the std::string value. Copying the std::string entails allocating another block of memory on the free store and copying the data into there. In addition, any members such as size are copied as your int is. When your stack variable goes out of scope, its int and the string will be destroyed, which causes the string to clean itself up, not touching the new copy in any way. What you're left with after that is the new object at the end of the vector with its own copy of the data.
If you'll excuse the poor diagram:
How does moving change this? Moving is simply an optimization to avoid unnecessary copies when possible. Copying is a valid move tactic, just not a good one if you can do better. Using std::move will ultimately cause the vector to move your stack object when creating the new object at the end of the vector. The int will still be copied because there's no optimization to be had there. However, the string can take advantage of the fact that this free store data is no longer needed. The new object can simply steal the pointer, copy the size etc., and tell the moved-from object not to clean up that free store data (transferring ownership).
If you'll excuse a slightly modified version of the original poor diagram:
We saved allocating a separate block for the string, but that's all. The other data was all still copied. The actual string data will be cleaned up by the vector element when it is removed or the vector is destroyed. The stack element now has nothing to clean up because moving has "stolen" the string data rather than copying it. You can think of it as nulling out the stack object's pointer, even though an implementation is free to represent an empty string differently.
What I said doesn't fully apply here because major implementations of std::string are clever enough to avoid extra allocations for small strings. That simply means the string data would need to be copied because as you say, it would die when the original object does. Any extra allocations are open to move optimizations, though.
As for your second example, there's no general optimization you can do to move a raw pointer; it's (usually) just 4 or 8 bytes to copy over. Moving that into the vector would copy the pointer value, leading to a dangling pointer within the vector once the function ends. The myStr pointer within the function would be destroyed when the function ends and not affect the vector in any way.
If NewStruct was entirely on the stack, the moving it would be no more efficient than copying it. To create a new object with the same contents as NewStruct would require copying everything from the stack to wherever the new object is.
Where move semantics are helpful are for objects that aren't entirely on the stack (or, to be more precise, don't have their entire contents stored in the object's fixed-size portion). For example, a std::string or a std::vector (or an object that contains them) will typically have a buffer that holds variable-length data that is allocated from the heap. Move semantics can transfer ownership of this buffer from the old object to the new object, saving the need to allocate a new buffer and copy the contents of the old buffer into it.
Move semantics have no effect on the object's scope. In this example, NewStruct has automatic scope, so it remains in scope until the end of the function it's declared in.
Moving the contents of NewStruct makes no difference, whatsoever. NewStruct's scope is still automatic, and it only gets destroyed when its function returns.
Declaring additional objects in automatic scope, in the same function, has the same practical effect as if NewStruct's contents were not moved anywhere.
"Moving" an object doesn't mean what it seems you think it means. It doesn't mean "this object vanishes in a puff of smoke, right this instant", and no longer exists, from that point on. It still does, after the move, in some valid but unspecified state. Move semantics basically means that you're telling your C++ compiler: "copy this object, but I don't care about its contents after the copy, so if this lets you do some efficient optimization, go knock yourself out". That's all. The object continues to exist, in some valid, but unspecified state, until the end of its scope.
isn't this information still placed on the stack?
Yes, it is. As an side: technically, there's no mention of "stack" anywhere in the C++ standard; a stack is just a practical implementation of automatic scope.
If I were to expand my use of the stack again, why wouldn't this
information be in danger of being overridden once the stack grew
Because NewStruct is still a valid object, and it still exists until the end of the function, at which point all objects on the stack get destroyed.

Stop destructor from calling when returning stack variable

I have a class List which allocates memory automatically to store a list of items.
It has a destructor which deallocates this memory:
List::~List()
{
free(memory);
}
This means, if I create a new list, I can use delete to call the destructor and free the memory.
The destructor will also be called once the variable is out of scope which is ALMOST always what I want. e.g:
int func()
{
List list;
list.push(...);
...
return 47;
}
However, what if I want to return that list?
List func()
{
List list;
return list;
}
I am alright with the list being copied because it's returned by value, and doesn't have much data to copy (only a few ints, and a pointer).
However, the memory that the list allocated and has a pointer to, contains a LOT of data.
Since I am returning the list, the list is being copied along with the pointer to this data.
Since the list is now out of scope, the destructor is called, which frees up the pointer to that data, even though the copy also has the pointer.
How do I prevent this destructor from being called?
1) There is probably a solution by creating a copy constructor, however, I don't want to do this because then all the data at that pointer will likely have to be copied which is a waste of time and temporarily requires double the memory to be allocated.
2) I know I could just create a pointer List* list and return that, but I want to avoid the necessity of allocating new memory for that list if possible, and also want to avoid wasting more memory for a pointer (8 bytes or something).
Thanks in advance,
David.
Assuming you’re using C++11 or later, you just create a move constructor which leaves the old list empty.
In order to avoid similar problems, you also need to delete the copy constructor, or actually write it so your class can be copied (don’t worry; in most cases, including the one you were worried about with returning from a function, the compiler will use the move constructor or get rid of the copy/move entirely, especially after C++17).
This is greatly simplified by storing the pointers as unique_ptr, which will help make sure you don’t make a mistake, and will mean you don’t need to explicitly write the copy or move constructors.
If you’re stuck on pre-C++11, you can’t do this, at least not without a small storage-space penalty. You’d need to use a reference-counting pointer like boost::shared_ptr (a version was added to the standard library with C++11, but it sounds like you would rather move-only semantics), which will only free the memory when it’s the last one one left referencing that memory. This makes copying, creating, and destroying lists slightly slower (since it needs to check/update the reference counter), and it takes some space to store the count, but these costs are relatively small compared to those of actually copying the list’s contents.
Note that in this case two copies always point to actually the same list. If you update one “copy”, the other also gets updated. This is usually not the behavior that users of your class would expect in C++.

Nulling pointers defined within function

In the codebase I inherited, I notice that the previous coder would null pointers init'ed within the function before the function closes.
Something like:
void MainClass::run() {
MyClass* _classPtr = GetClassPtr(); // Assume no problems here.
// do stuff to _classPtr
_classPtr = nullptr; // Is this even necessary?
return;
}
I find it unnecessary since the pointer's memory (Not the object itself, just the pointer) should be freed up at function close. Is that true?
It's not necessary to do this. When the function goes out of scope the the memory used by the pointer will be release regardless of what it's set to. However, the object pointed to will not be released.
This is probably just a coding standard adopted by the previous developer.
The variable _classPtr will disappear when it goes out of scope, which happens when the function returns, so no there's no need to reassign it to be a null pointer.
Talking about returning, once the function reaches its end, it will return automatically, there's no need for an explicit return at the end of a function without return value.
You are correct, it's just a personal programming practice, and does not accomplish anything. The compiler is likely to optimize the whole thing away, so this will not actually end up producing any actual code at run time.
It could also be remnants of prior versions of this functions, that might have had more code that followed, but it was removed, except that the re-initialization part was forgotten.
only the memory allocated in the heap i.e memory initialized using new operator are not freed at closing the functions .
Local variables or auto variables defined inside the functions would be automatically freed .
One useful pattern that could be used is that each pointer is nulled immediately after the resources it holds are recycled.
If a pointers resources need not be recycled, and is no longer going to be used, it is nulled without the recycling.
Then, at any point in the function while debugging or reasoning about it, the only non-zero pointers are those that have "live" (valid) memory, or are in the midst of being cleaned up.
If you know a given pointer always holds a resource, you can "blindly" do an "if (ptr) delete ptr; ptr=0;` without worrying that someone has already cleaned it up.
On the other hand, a line of code saying ptr=0; without a delete right next to it tells you that the pointer was not owning any memory, as opposed to it being leaked. (or, someone deleted the delete accidentally, which is less likely)
You might think this is inefficient; but if the compiler can prove that nobody will read a pointer after a point where you set it to zero, the compiler can (under the as-if rule) remove the nulling of the pointer.
So the cost is zero, if the compiler can figure it out. If the code is complex enough that the compiler cannot figure it out, maybe nulling it redundantly would be a good idea anyhow?

std::move a stack object (to a different thread)

So there are two things that I'm not sure.
If I do something like this:
void sendToDifferentThread(SomeClass &&obj);
...
{
SomeClass object;
sendToDifferentThread(std::move(object));
}
What will happen? How can there ever only be one copy of object if it's created on the stack, since when we go out of the enclosing scope everything on stack is destroyed?
If I do something like this:
SomeClass object;
doSomethingOnSameThread(std::move(object));
What will happen if I do something in the current scope to object later? It was "moved away" to some other function, so did the current function "lose" ownership of it in some way?
In C++ when an object is constructed, memory is allocated at the same time. If the constructor runs to completion (without throwing) then the object is "alive". At some point if it is a stack object and goes out of scope, or its a heap object and you call delete, its destructor is called and that original memory is freed, and then the object is "dead". C++11 std::move / move constructors don't change any of this. What a move constructor does is give you a way and a simple syntax to "destructively" copy objects.
For instance if you move construct from a std::vector<int>, instead of reading all the ints and copying them, it will copy the pointer and the size count instead to the new vector, and set the old pointer to a nullptr and size to 0 (or possibly, allocate a (tiny) new vector of minimal size... depends on implementation). Basically when you move from something you have to leave it in a "valid", "alive" state -- it's not "dead" after you move from it, and the destructor is still going to be called later. It didn't "move" in the sense that it's still following the same lifetime and now it's just "somewhere else in memory". When you "move" from an object, there are definitely two different objects involved from C++'s point of view and I don't think you can make sense of it after a certain point if you try to think of it as though there's only one object that exists in that scenario.

C++ initialization list, Class in class (Aggregation)

I am writing a dish washer program, Dishwasher has a pump, motor, and an ID. Pump, motor, date, time are other small classes which Dishwasher will use. I checked with the debugger but when I create the Dishwasher class, my desired values aren't initialized. I think I am doing something wrong but what? :(
So the Dishwasher class is below :
class Dishwasher {
Pump pump; // the pump inside the dishwasher
Motor motor;// the motor inside the dishwasher
char* washer_id;//011220001032 means first of December 2000 at 10:32h
Time time_built;// Time variable, when the Dishwasher was built
Date date_built;// Date variable, when the Dishwasher was built
Time washing_time; // a time object, like 1:15 h
public:
Dishwasher(Pump, Motor, char*, float);
};
This is how I initialize the Class :
Dishwasher::Dishwasher(Pump p, Motor m, char *str, float f)
: pump(p), motor(m), max_load(f)
{
washer_id = new char [(strlen(str)+1)];
strcpy (washer_id,str);
time_built.id2time(washer_id);
date_built.id2date(washer_id);
}
This is how I create the class:
Dishwasher siemens(
Pump(160, "011219991143"),
Motor(1300, "081220031201"),
"010720081032",
17.5);
Here is the full code if you would like to see further, because I removed some unused things for better readability : http://codepad.org/K4Bocuht
First of all, I can't get over the fact you're using a char* for shuffling strings around. For crying out loud, std::string. It's a standardized way of dealing with a string of characters. It can wiggle its way out of anything you throw at it, efficiently. Be it a string literal, a char array, a char* or yet another string.
Second of all, your professor needs professional help. It's one thing to teach the "lower level intuition" with something more raw, but to enforce this on students who ought to be studying C++ is plain bad practice.
Now, onto the problem at hand. I'll just take the toplevel example, namely the raw, "naked" pointer lying in your Dishwasher constructor, the char* str. As you know, that's a pointer, namely a pointer to a type char. A pointer stores the memory address of a variable (the first byte of any type of variable in question, which is the lowest addressable unit in memory).
That distinct difference is very important. Why? Because when you assign a pointer to something else, you're not copying the actual object, but only the address of its first byte. So, in effect, you just get two pointers pointing to the same object.
As you are, no doubt, a good memory citizen, you probably defined a destructor to take care of this:
washer_id = new char [(strlen(str)+1)];
You're basically allocating strlen(str)+1 bytes on the heap, something that is unmanaged by the system and remains in your capable hands. Hence the name, a heap. A bunch of stuff, lose the reference to it, you'll never find it again, you can just throw it all away (what actually happens to all the leaks when the program returns out of main). Therefore, it is your duty to tell the system when you're done with it. And you did, defined a destructor, that is.
A big, nasty but...
But... There is a problem with this scheme. You have a constructor. And a destructor. Which manage resource allocation and deallocation. But what about copying?
Dishwasher siemens(
Pump(160, "011219991143"),
Motor(1300, "081220031201"),
"010720081032",
17.5);
You are probably aware that the compiler will try to implicitly create a basic copy constructor, a copy assignment operator (for objects that have already been constructed) and a destructor. Since an implicitly-generated destructor has nothing to release manually (we discussed dynamic memory and our responsibility), it is empty.
Because we do work with dynamic memory and allocate varying size blocks of bytes to store our text, we have a destructor in place (as your longer code shows). That is all well, but we still deal with an implicitly generated copy constructor and a copy assignment operator which copy the direct values of variables. As a pointer is a variable whose value is a memory address, what an implicitly-generated copy constructor or copy assignment operator does is just copying that memory address (this is called a shallow copy) which just creates another reference to a unique, singular byte in memory (and the rest of the contiguous block).
What we want is the opposite, a deep copy, to allocate new memory and copy over the actual object (or any compound multi-byte type, array data structure etc.) stored at the incoming pointer's memory address. This way, they will point to distinct objects whose lifespan is tied to the lifespan of the enveloping object, not the lifespan of the object from which is being copied from.
Observe in the example above that you're creating temporary objects on the stack which are alive for the time the constuctor is in action, after which they are released and their destructors are invoked.
Dishwasher::Dishwasher(Pump p, Motor m, char *str, float f)
: pump(p), motor(m), max_load(f)
{
washer_id = new char [(strlen(str)+1)];
strcpy (washer_id,str);
time_built.id2time(washer_id);
date_built.id2date(washer_id);
}
The initialization list has an added benefit of not initializing objects to their default values and then performing the copy, since you have the honor of directly calling the copy constructor (which in this case is implicitly generated by the compiler):
pump(p) is basically invoking Pump::Pump(const Pump &) and passing in the values the temporary object has been initialized with. Your Pump class contains a char*, which holds the address of the first byte to the string literal you shoved into the temporary object: Pump(160, "011219991143").
The copy constructor takes the temporary object and copies all the data explicitly-available to it, that means it just takes the address contained in the char* pointer, not the entire string. Therefore, you end up pointing from two places to the same object.
Since the temporary objects live on the stack, once the constructor finishes dealing with them, they will be released release and their destructors invoked. These destructors actually destroy the string along with them, which you placed while creating the Dishwasher object. Now your Pump object within the Dishwasher object holds a dangling pointer, a pointer to a memory address of an object lost in the endless memory abyss.
Solution?
Write your own copy constructor and copy assignment operator overload. On the example of Pump:
Pump(const Pump &pumpSrc) // copy constructor
Pump& operator=(const Pump &pumpSrc) // copy assignment operator overload
Do this foreach class.
Of course, in addition to the destructor, which you already have. These three are the protagonists over a rule of thumb called "The Rule of Three". It states that if you have to declare any of them explicitly, you probably need to explicitly declare the rest of them, too.
The probably part of the rule is just a void of responsibility, basically. The need for explicit definitions is actually quite obvious when you get further down the road with C++. For example, thinking about what your class does is a good way of determining whether you need everything defined explicitly.
Example: Your classes depend upon a naked pointer which points to a piece of memory and the memory address is all it pulls along. It's a simple typed variable which holds only a memory address to the first byte of the object in question.
In order to prevent a leak upon destroying your object, you defined a destructor which frees the allocated memory. But, do you copy the data between objects? Very likely, as you've seen. Sometimes you're going to create a temporary object which will allocate data and store the memory address into a pointer data member whose value you will copy. After a while, that temporary is bound to be destroyed at some time, and you will lose the data with it. The only thing you'll have left is the dangling pointer with a useless and dangerous memory address.
Official disclaimer: Some simplifications are in place to prevent me from writing a book on the subject. Also, I always try to concentrate on the question at hand, not the code of the OP, which means I do not comment on the practices employed. The code might be horrible, beautiful or anything in-between. But I do not try to turn the code or the OP around, I just try to answer the question and sometimes suggest something I think could be beneficial to the OP in the long run. Yes, we could be precise as hell and qualify everything... We could also use Peano axioms to define the number sets and the basic arithmetic operations before we boldly try to state that 2 + 3 = 3 + 2 = 5, for the same amount of fun.
You have violated the Rule of Three. You can solve your problem either of these two ways:
Never use naked pointers. In your case, you can do that by replacing char* in every instance with std::string.
Implement a copy constructor, and a copy assignment operator, each of which must perform a deep copy, and a destructor.