0

This got marked as a duplicate because it was claimed that it is already answered here. I don't feel that my question is answered at all there since that post doesn't deal with vectors or similar containers at all. None of the comments on my question answer it either. So, on the theory that I buried my question too deep I'll put it right up front before my example.

When we invoke .clear() on a vector it calls the destructor on each element of the vector. The "well known" exception is when it is a vector of pointers. My example below suggests that when we .clear() a vector of vectors the destructor apparently doesn't get called on the vectors that are its elements, since I can still access them afterwards. Why? In what way is a vector "kind of like" a pointer so that the destructor does not get called on it?

To be clear about a few things:

  • I know a vector is not a pointer. But I'm rather unclear about what the "guts" of a vector really look like, which is probably why I don't understand this.

  • Also, I know this is undefined behaviour. My question is, why is it undefined? I would have hoped that this would have led to explicit errors, not undefined behaviour. I generally hope that when I do something stupid the programming language should kick me in the butt. Here C++ is failing to punish me for doing something very dumb, and I'm curious about why. (Yes, I know the makers of the C++ standard can't predict every foolish thing a programmer like me might do, and it is our responsibility to not be foolish. But the continued accessibility of these vectors after a .clear() is pretty counterintuitive.)

It came up because of unexpected behaviour in a very long program and I traced the problem to this. I fixed it, but am still puzzled by why that program failed subtly rather than spectacularly.

OK, here's the original question:


There are a couple of other questions here related to this such as this one and this one but I don't think they quite address the issue I'm wondering about. My understanding is that calling clear on a vector calls the destructor on each element of the vector (but see below for an exception to this). So, with a vector of vectors I would have assumed that the destructor would be called on each vector within the vector of vectors. That appears to be incorrect. I know several ways to write my code to deal with it. But I'm still a bit puzzled by the behaviour. This code shows the behaviour:

using namespace std;

#include <iostream>
#include <vector>


void writeMat(vector<vector<double>> &vec);

int main()
{
  vector<vector<double>> b;

  writeMat(b);

  // Trying to get b[0].size() here results in a core dump, as expected.

  vector<double> btry1 = {1.0, 2.0};
  vector<double> btry2 = {3.0, 4.0};

  b.push_back(btry1);
  b.push_back(btry2);

  cout << "Added some stuff to the vector." << endl;
  writeMat(b);

  b.clear();
  cout << "Cleared the vector.  Now it is: " << endl;
  writeMat(b);

  cout << "But final check...  b.size() = " << b.size() << " and b[0].size() = " << b[0].size() << endl;

  return 0;
}


void writeMat(vector<vector<double>> &vec)
{
  cout << "vec.size() = " << vec.size() << endl;
  for (int i = 0; i < vec.size(); i++)
    {
      cout << "vec[" << i << "].size() = " << (vec[0]).size() << endl;
    }

  cout << "vec is: " << endl << "--------------------" << endl;
  for (int i = 0; i < vec.size(); i++)
    {
      for (int j = 0; j < vec[i].size(); j++)
    {
      cout << vec[i][j] << "  ";
    }
      cout << endl;
    }
  cout << "--------------------" << endl;
}

Generates output:

vec.size() = 0
vec is: 
--------------------
--------------------
Added some stuff to the vector.
vec.size() = 2
vec[0].size() = 2
vec[1].size() = 2
vec is: 
--------------------
1  2  
3  4  
--------------------
Cleared the vector.  Now it is: 
vec.size() = 0
vec is: 
--------------------
--------------------
But final check...  b.size() = 0 and b[0].size() = 2

I would expect that calling b[0].size() after calling b.clear() would result in a core dump just like it would if I called it before any elements had been added to b. But instead it returns the value 2, which suggests that although b.size() is now zero, b[0] is still hanging around in memory. Indeed, modifying the above code I find that after b.clear(), it will still report, for example, that b[0][1] is 2.

I see here that for a vector like vector<MyClass>, the destructor gets called on each element of the vector when clear() is called but for a vector of pointers like vector<MyClass*> it does not. So I guess a vector is "more similar" to a pointer in some way than it is to an object. It has never been very clear to me what containers like vector are in relation to more primitive types. I think of a vector as as an old style array (not std::array, an old array like we would have used in C, 20 years ago...) with a rather thin wrapper of methods to make it friendlier. But that can't be quite right because passing a pointer to a vector has a very different effect from passing a pointer to an array.

So, I guess my question is roughly this: A vector is not a pointer. But when clearing a vector of vectors the behaviour seems to be similar to clearing a vector of pointers. So what is the similarity that causes this behaviour? I realize that this is likely to get into the nitty gritty of what the block of memory set aside for a vector will look like as opposed to what the block of memory set aside for an array would look like.

It occurs to me that this might be specific to the implementation of C++. I'm using g++ in a Linux OS.

gleedadswell
  • 201
  • 3
  • 12
  • http://www.cplusplus.com/reference/vector/vector/shrink_to_fit/ – Gillespie Jul 03 '19 at 18:22
  • 5
    You have [nasal demons](https://en.wikipedia.org/wiki/Undefined_behavior). – Fred Larson Jul 03 '19 at 18:22
  • 5
    "My drivers license was revoked, but I can still get in a car and drive it. Why?" – PaulMcKenzie Jul 03 '19 at 18:23
  • As hinted by Fred, you just invoke undefined behaviour. If you turn on diagnostic mode for your standard library, it would tell you that you are accessing a vector out of bounds. – Ulrich Eckhardt Jul 03 '19 at 18:24
  • This doesn't address the question, but `writeMat` should take its argument as a `const&`, not a plain `&`. That way it's clearer that `writeMat` doesn't modify the argument. – Pete Becker Jul 03 '19 at 18:24
  • "I would expect that..." Why would you expect that? – Eljay Jul 03 '19 at 18:27
  • 1
    *"I would expect that calling b[0].size() after calling b.clear() would result in a core dump"* Accessing an element out of bounds is Undefined Behavior. It's not required to crash, anything can happen. For example, an `std::vector` doesn't release it's memory when `clear`ed and an implementation could just set it's size member to zero and destructs it's elements will not touch the pointer to the data. Since `operator[]` isn't required to check bounds, your operation may just be reading from memory your process has access to but that doesn't contain any object. – François Andrieux Jul 03 '19 at 18:28
  • 1
    *I would expect that calling b[0].size() after calling b.clear() would result in a core dump* -- "I would expect that getting in a car and driving without a valid license would have a policeman show up and stop me immediately". I like these analogies. – PaulMcKenzie Jul 03 '19 at 18:29
  • 1
    @PaulMcKenzie Another option is you drive through the woods, and won't meet a p'lice officer lurking at the highway. – πάντα ῥεῖ Jul 03 '19 at 18:48
  • 1
    BTW, your program when run under Visual Studio will show a "debug assertion" dialog when run under the debug runtime and stops right there when you access the vector. On the other hand, when run under the non-debug / release runtime, the program gives the impression of running "ok". That is an example of undefined behavior right there. – PaulMcKenzie Jul 03 '19 at 18:50
  • @πάνταῥεῖ -- Yes, sometimes it's simpler to get the point of "undefined behavior" across by coming up with real life scenarios. – PaulMcKenzie Jul 03 '19 at 18:57
  • OK, but none of these answers really address the question that I posed. Perhaps the question was buried at the end of too much other stuff. I understand that this is undefined behaviour. I understand that I am accessing an array out of bounds. The latter is why I would expect a core dump. But my question was, more or less, "Why doesn't the destructor get called on vectors that are the elements of this vector of vectors?" I'll edit the question and put that right up front. – gleedadswell Jul 04 '19 at 19:23
  • @gleedadswell, The correct answer is that this is undefined behavior but let's ignore that for a second. The vector is more or less a wrapper around some elements and you can think of the elements as just some bytes in memory somewhere. When the vector is cleared there is no requirement to alter that memory, the size just has to report 0. If nothing else changes that memory, you interpret the memory to still be a valid element, and you use a method of that element then it might behave as if it were still valid – asimes Jul 04 '19 at 20:23
  • Edited to clarify the question. I do not feel it is a duplicate and have explained why. I understand that the vector is a wrapper around some elements. But my question is about why the destructors are not called when the elements are themselves vectors. Or are they? Am I misunderstanding what the effect of the destructors being called would be? Nobody has yet addressed my main question which is about why the destructors are not called. They would be called if the elements of the vectors were "ordinary objects". Why are they not called when the elements are vectors? – gleedadswell Jul 05 '19 at 20:18

0 Answers0