So, I mean, WHY in C++ it doesn’t work!?
You didn’t show the C++ equivalent. And the C++ equivalent seems to work fine for me.
I don’t know where I’ve copied the object “Texture”, I’ve only created two object and set them parameters D;
That’s because you’re a Java programmer learning C++. You’re conditioned to think in terms of references to objects, while C++ is a value-oriented language.
In Java, a variable isn’t an object; it is a reference to an object. In C++, unless a variable is explicitly a pointer or reference, that variable actually is an object.
Take this lines:
var = SomeFunc(...);
In Java, this will stick a new reference into var
. In C++, unless var
is a pointer, this will always copy the value returned from SomeFunc
. So whatever data SomeFunc
returned must be copied by some process into the object currently stored by var
.
The same is true of:
SomeFunc(..., var, ...);
In Java, SomeFunc
’s parameter is initialized with the same reference as var
. And thus, SomeFunc
in Java will reference the same object that the caller is referencing. If SomeFunc
modifies this parameter, then the caller will see the modification of that object.
In C++, unless the parameter is explicitly a reference, this will copy var
into SomeFunc
’s parameter. Which means that if SomeFunc
modifies the parameter, the caller will not see the modification. Because what’s being modified is a copy, not the original.
So what does it mean to copy an object? Well, that depends. In C++, by default, all objects are copyable. But because there is a default, it has no idea what the objects internals actually mean. Therefore, default copying simply does a member-wise copy operation.
However, C++ does offer a way to explicitly specify how a copy operation works. And here’s where we get into your Java example and the problem you’re having in code.
When initializing your objects, your Java example did this:
Person mark = new Person("Mark", "McDonald");
If you did an exact replica of this in C++, you’d get a compile error. Obviously, that’s because new
returns a pointer, while Person mark
is an object variable, not a pointer. So you remove the new
and the compiler shuts up:
Person mark = Person("Mark", "McDonald");
And this would work just like your Java code.
(Pedantic note: I am now about to say something that is completely untrue of C++. However, I’m lying for a very good reason: the truth is complicated and only muddles the explanation of the problem.)
Given what I’ve told you, this creates a new Person
object and then copies it into mark
(see pedantic note above). However, if you change the Ideaone code to initialize the two variables like this, rather than the way I did originally, you’ll find that your code works just fine.
So why does this example work when your Texture object copying doesn’t? Well, that has to do with a concept called “ownership”. And this concept is why copying is something you have to think about in C++.
Deep down, you probably know that a string type has to allocate memory. And because C++ doesn’t have a garbage collector to “save” you from memory leaks, someone has to both allocate that memory and deallocate that memory when you’re finished with it. This is the primary job of std::string
.
The constructor of std::string
allocates memory. The destructor of std::string
deallocates any memory previously allocated by the constructor. So that means, somewhere inside std::string, it stores a pointer to some array of characters. One of its member variables is a char*
. It’s allocated in the constructor, and deallocated in the destructor.
The std::string
instance is said to own this array of characters. It created it, and it claims responsibility for destroying it.
So… what happens when you copy a string?
If std::string
used the default copy constructor (which just does a member-wise copy operation), what would this do:
std::string obj = std::string("A String");
This creates a string, then copies that string into obj
(remember the pedantic note). Which means that there are now two std::string objects: the one created by std::string("A String")
and obj
. So… what happens to the first object?
The first object is called a “temporary”; it is so named because it is… temporary. It will last only for as long as it needs to. And this temporary does not need to last longer than it takes to copy its data into obj
. Therefore, the final step of this line is to destroy the temporary.
But wait. We just said that std::string’s destructor will deallocate its array of characters. And we just copied a pointer to that array of characters into obj
. If the temporary is destroyed, then the array will be deallocated. But obj
still has a pointer to it.
Which would mean that obj
has a pointer to deallocated memory. Therefore, any attempt to use obj
will fail.
And yet, this code works (again, ignoring the lie); it is legal to copy std::string to your heart’s content. Why?
Because std::string implements “value semantics”.
See, every std::string instance knows that it is responsible for its own memory. Therefore, it overrides the default copying mechanics, to ensure that every instance remains independent on copying. When you copy a std::string, the destination string will allocate its own array of characters, then copy the characters from the incoming string into that block. After the copy, both the source and destination strings have an array of characters, but they’re a separate array. Thus, you can delete one array without affecting the other.
The ability to copy an object such that the resulting object si completely separate from the original is called “value semantics”. It works just like this:
int val1 = 1;
int val2 = val1;
val2 = 5;
printf("%i", val1);
This will always print 1. Because val2
is a completely separate value from val1
; changing val2
will never affect val1
’s value. The copy stored only the current value of val1
into val2
; any subsequent changes to one will not affect the other.
std::string
is designed to work the same way. Value semantics, for types that own some resource, means that copying them must copy the resource as well. Not merely copying the reference/pointer to that resource. But the resource itself.
Person
inherits this because the default copy mechanism copies all of the elements member-wise. And this member-wise copy can use the overridden copy mechanism of std::string
, even though Person
itself uses the default.
Which brings us at last to your Texture
class. This has the exact same problem that std::string
would. Just like std::string
, it stores a “pointer” to some other data. And therefore, its destructor needs to delete that data. Texture
owns the OpenGL texture object it creates.
Which means, just like std::string
, you can no longer use the default copy semantics. Otherwise, after a copy, you will have two objects that own the same OpenGL texture object. One of them will be destroyed first; it doesn’t matter which one, because once it happens, the other will have an OpenGL texture object that has been destroyed.
My suggestion above was to implement something called “move semantics”. This is where you are allowed to “copy” the object, but only by stealing the data. This represents a transfer of ownership from one object to another. First, object A owned it, but then you transfer it to object B. After the transfer, object B alone owns the memory; object A is left with nothing.
This represents “unique ownership” of the memory. At any time, only one object ever claims ownership of the resource.
Pedantic truth: The reality is that a statement of the form Typename varName = Typename(...);
never copies anything. It initializes varName
directly in place, without calling any copy operation. Now, in order for this statement to work, varName
must have a copy constructor which can be called from this location. But it won’t actually use the copy constructor. Only one object will be created, no temporary will be made, and thus no errors could come from it.
However, it’s easier to talk about that case than Typename varName = ...; varName = Typename(...);
, which would legitimately be copying the value.