Vector of pointers: Why does changing the pointer externally not change the vector element?

Viewed 657

Please look at the following example:

#include <iostream>
#include <string>
#include <vector>


class Base {
public:
    virtual void price() const = 0;
};

class D1 : public Base {
public:
    virtual void price() const {
        std::cout << "Price of D1\n";
    }
};

class D2 : public Base {
public:
    virtual void price() const {
        std::cout << "Price of D2\n";
    }
};

int main()
{
    std::vector<Base*> v;
    
    
    Base* b = new D1();
    v.push_back(b);
    v[0]->price(); // "Price of D1\n" - OK!
    
    b = new D2();
    v[0]->price(); // "Price of D1\n"?! Why not "Price of D2\n"
}

A simple base class has two derived classes. In my main() I declare a vector containing pointers to the base class and fill it with a pointer to an object of D1.

When I change b to a pointer to D2, b = new D2();, why doesn't that change the element v[0] accordingly? Aren't they supposed to point to the same thing?

4 Answers

When I change b to a pointer to D2, b = new D2();, why doesn't that change the element v[0] accordingly? Aren't they supposed to point to the same thing?

Because v.push_back(b); stores a copy of the pointer b in your vector. So if you change b to point to something else afterwards, it will have no effect whatsoever on v[0]

You can simplify it and see it like this:

int *ptr = new int;
int *ptr_copy = ptr;

*ptr = 2; //both *ptr and *ptr_copy have value: "2"

ptr = new int; //ptr now points to some other memory
*ptr = 5;      //*ptr = 5, but *ptr_copy will still be "2"

Now if you really want the changes to be reflected even when you change the pointer, you need another level of indirection i.e., "pointer to pointer":

int main()
{
    std::vector<Base**> v; //vector of Base**   
    
    Base* b = new D1();
    v.push_back(&b);
    (*v[0])->price(); // "Price of D1\n" - OK!
    
    b = new D2();
    (*v[0])->price(); // "Price of D2\n"
}

I'll try to explain it on a lower level.

First of all, understand that a pointer is essentially simply a variable that holds a value, which happens to be an address in memory.
Let's simplify things and assume that every type has the same size and that there are only 5 addresses of memory (on the heap). (Also I will ignore that the array will allocate on the heap too.)
We now execute

Base* b = new D1();

Let's say that the allocated memory is at the address 3. b is now a variable that simply holds the value 3. Our (heap) memory looks like this:

0: ?
1: ?
2: ?
3: variable of type D1
4: ?

Then we continue with

v.push_back(b);

We now have that the array v holds one entry of value 3.

b = new D2();

We now allocated a new part of memory, let's say at address 1:

0: ?
1: variable of type D2
2: ?
3: variable of type D1
4: ?

b now stores this address, that is, the value of b is 1. If we look at v, we haven't changed it. It contains one entry with value 3.

v[0]->price();

This gives us a pointer with value 3, which we dereference and print. Given our memory map above, what is stored there is a variable of type D1.

Did this clarify things for you?

I also extended your code a little bit to demonstrate this with realistic addresses:
http://www.cpp.sh/3s4b4
(If you run that code, note that addresses allocated right after each other tend to be almost similar, often only differing by a single digit, thus look closely. For example, I got 0x1711260 and 0x1711220.)


If you really need to do what you expected, you could store a vector over Base** and store the address of b in the vector:

std::vector<Base**> v;

Base* b = new D1();
v.push_back(&b);
(*v[0])->price(); 

b = new D2();
(*v[0])->price(); 

Implemented on http://www.cpp.sh/8c3i3. But I wouldn't recommend that if you don't absolutely need to, lacks readability and unnecessarily confuses. (Note that the first pointer is not deleted in this example and unreachable after changing b, thus we'd leak memory.)

Try this variation

vector<int> v;
int b = 123;
v.push_back(b);
cout << v[0]; // prints 123

b = 456;
cout << v[0]; // still prints 123

That changing b doesn't change v[0] is hopefully obvious, because the value of v[0] is a copy of the value of b. But then compare with your code, what's the difference? There is none, it's exactly the same situation v[0] is a copy of b and changing b doesn't change v[0]. The fact that your case involves pointers make no difference at all (in this regard) because there's nothing special about pointers.

When you insert a pointer into a std::vector of pointers, you copy that pointer into the container. After

std::vector<Base*> v;

Base* b = new D1();
v.push_back(b);

you have two pointers that refer to the D1 instance: b and vec[0]. Now if you go on with

b = new D2();

you only overwrite one of these two, that is obviously b, but not vec[0]. Hence, accessing vec[0] gives you a reference to the pointer stored there in the first place.

Related