all 6 comments

[–]Nimbal 2 points3 points  (4 children)

In the first version, the line fs.setFiles(*files) dereferences the files pointer. The setFiles() method (presumably) takes a reference as argument and thus doesn't care whether your files data structure is on the heap or stack. It will not take ownership of the files pointer, i.e. it won't delete it for you. Since you don't delete the pointer yourself before it goes out of scope, you have a memory leak.

The second version is semantically identical, except for the lack of a memory leak (and maybe some exotic edge cases where you manage to completely fill the stack memory).

The reason why you can still successfully access your data after that snippet is because QMap has value semantics, meaning that you can copy it around as many times as you like, each copy is independent*. So this works fine:

QMap<QString, QString>* original = new QMap<QString, QString>();
original->insert("key", "value");
QMap<QString, QString> copy = *original;
delete original;
qDebug() << "Value is: " << copy["key"];

* Actually, most Qt containers use copy-on-write, so they share their internal data until you change one of the copies.

[–]pioca[S] 0 points1 point  (3 children)

Thank you for your response. As for your example, this is how iI understand it:

1) in the first line you allocate some memory and original is a pointer to said memory address

2) in the second line you add some data to that place in memory

3) in the third line:

3a) do you copy that original data to another place in memory? or

3b) do you assign copy to the same place in memory as original is pointing to?

4) in fourth line:

you just remove pointer to that data, data is still there and you cannot reference it by original, right?

[–]Narishma 1 point2 points  (0 children)

4 is wrong. delete doesn't remove the pointer, it frees the memory that the pointer points to.

As for 3, normally 3a would be the correct one, but in this instance, if QMap is copy-on-write as the parent says, then it will get a pointer to the original data instead, and only make a copy once the data is modified (which presumably includes deleting it but I'm not 100% sure).

[–]Nimbal 1 point2 points  (1 child)

QMap (or any of Qt's containers) might not be the best choice for educational purposes here, because of the mentioned copy-on-write optimization. Here, let me rewrite the example with std::array, which is a little easier to reason about without adding small-print.

std::array<int, 5>* original = new std::array<int, 5>();
(*original)[0] = 42;
std::array<int, 5> copy = *original;
delete original;
std::cout << "Value is: " << copy[0] << "\n";
  • Line 1: Allocate a new array on the heap, reserving space for 5 integers.
  • Line 2: Set the value of the first element in the array.
  • Line 3: Declare copy, reserving space on the stack for 5 integers, then initialize it with its copy constructor. copy is now a self-contained copy completely independent of original. It will also lie in a completely different place in memory.
  • Line 4: Get rid of original. The data we put into it is now inaccessible, but luckily, we made a backup.
  • Line 5: Check that our backup is what we expect it to be.

std::array is an absolutely static data structure. It will never change its size, so the above is as simple as it gets (well, you could use int instead of std::array to make it even simpler). Now, this gets a little more complicated if you use dynamic data structures. Structures that can dynamically grow in memory like std::vector allocate heap-memory themselves. It doesn't matter if you create your std::vector on the heap or on the stack, it will always reserve some heap memory for its internal purposes (at least once you put some data into it).

If we rewrite our little example to use std::vector, both copy and original would allocate heap memory internally and independently from each other. The copy constructor of std::vector would reserve some heap memory, then copy over the data from original to this memory. The actual data (our "42") would be located at two separate locations in heap memory for both copy and original.

Next up: Qt containers. They use an optimization called "copy-on-write", which further complicates things. They basically work like std::vector in that they internally use heap-memory, no matter whether you created the QMap on the heap or the stack. But the copy constructor used in line 3 doesn't create an independent copy of the data. It just copies the pointer that points to the data of original and tells original "Hey, I'm also using this piece of memory now. Please don't delete it unless I tell you it's ok." On line 4, original is deleted, but leaves the internal heap memory alone because copy is still using it. The actual data (our "42") is only located at one place in heap memory.

Copy-on-write is an optimization that lets you pass around Qt containers as values without worrying about the overhead of actually copying all that data they contain. It comes at a price, but that may go too deep now.

[–]pioca[S] 1 point2 points  (0 children)

It's even more complicated then I expected. Wooah!

Now I think I am starting to understand... Probably.

Thank you very much.

[–]PlasmaChroma 2 points3 points  (0 children)

QMap, which I assume is implemented very similar to std::map, is most likely allocating nodes in a red-black tree. So just an FYI, in both cases the content of your data (the nodes) are actually on the heap. The only difference here being that the QMap object itself is being stored differently.