Why are all of my "this" pointers the same value?
Asked Answered
G

1

41

I'm working on the following code snippet:

#include <iostream>                                                                                                                                                                                            
#include <vector>

class myclass
{
    public:
        myclass()
        {
            std::cout << this << std::endl;
        }
};

int main()
{
    std::vector<myclass> v;
    for(uint32_t i = 0; i < 10; i++)
        v.push_back(myclass());
    return 0;
}

I'm compiling the code using g++ main.cpp. When I execute the compiled binary, I get:

0x7ffebb8f8cab
0x7ffebb8f8cab
0x7ffebb8f8cab
0x7ffebb8f8cab
0x7ffebb8f8cab
0x7ffebb8f8cab
0x7ffebb8f8cab
0x7ffebb8f8cab
0x7ffebb8f8cab
0x7ffebb8f8cab

My question is why are all the this pointers identical? If I'm creating 10 different objects of the same class, there should be 10 distinct this pointers. Right?

As far as I understand, my code is currently using the references of the same objects to populate the vector v. However, what I want are 10 distinct objects of myclass. How can I get this? This code is a part of a larger project and that project has some issues with new and delete. So I cannot use that API. What am I doing wrong and how can I fix this?

Goldy answered 18/12, 2023 at 21:41 Comment(14)
Use emplace_back. Or add logging move & copy constructors. Currently you're printing out the default constructor of a temporary, which is then moved (without logging).Godric
myclass() makes a temporary that exists only long enough for it to be copied into v. This allows the system to keep reusing that space in storage over and over.Illinium
If you look at the items in v you will see different addresses: godbolt.org/z/1Enn9zMnP Also added a copy constructor so you could get a better look at everything that's going on, including the vector resizes.Illinium
Thanks. emplace_back worked. Just to be sure, the problem was that push_back was using the default copy constructor of the class. Since the object that was being copied was getting destroyed in every iteration of the loop, the next time it was allocated, it was getting allocated at the same address resulting in identical values of the this pointer. Am I correct?Goldy
Yes, exactly. push_back has to make a copy (because it takes its parameter by value). This is why emplace_back was invented - it's more efficient.Mi
@PaulSanders push_back does not take in its parameter by value, but rather by const reference. But yes, it does make a copy of the parameter when inserting into the vector.Rau
Add a copy constructor and show the value of this.Reticulation
@PaulSanders it has to make a copy regardless of how it takes its parameter because elements in a vector have to be contiguous and that can't be given to it via any mechanism of it being handed a constructed object.Vinegarette
@Vinegarette Actually, that's not the case. emplace_back constructs an object in place, passing the parameters passed in (if any) on to the constructor. A bit of simple template magic + placement new, I've had to code it myself.Mi
@RemyLebeau Thank you for the correction. Might need to revisit my home grown Vector class ... No, it's fine. Like a lot of things, I used to know that.Mi
@PaulSanders I was very clear about it being handed "a constructed object". emplace_back does not hand it a constructed object. You cannot have a this unless you have a constructed object.Vinegarette
@Vinegarette Oh, OK. I think Remy put it better.Mi
@RemyLebeau, here push_back gets an r-value reference and moves. Which doesn’t make a difference for this class, but still.Okhotsk
@CarstenS No, it's fine, I boobedMi
C
52

As noted in comments, you're seeing the this pointer for a temporary and then copying the temporary into the vector. Since the temporary is temporary your system is reusing the same memory location on each loop iteration.

However, if you print the this pointer for the vector elements, you will notice they are placed in sequential memory locations as you would expect for a vector.

#include <iostream>                                                                                                                                                                                            
#include <vector>

struct myclass {
    myclass() {
        std::cout << this << std::endl;
    }

    void print() const { 
        std::cout << this << std::endl; 
    }
};

int main() {
    std::vector<myclass> v;
    for (uint32_t i = 0; i < 10; i++)
        v.push_back(myclass());

    std::cout << "\n";

    for (auto &x : v) 
        x.print();
        
    return 0;
}

Output:

0x7ff7bfe6f5a8
0x7ff7bfe6f5a8
0x7ff7bfe6f5a8
0x7ff7bfe6f5a8
0x7ff7bfe6f5a8
0x7ff7bfe6f5a8
0x7ff7bfe6f5a8
0x7ff7bfe6f5a8
0x7ff7bfe6f5a8
0x7ff7bfe6f5a8

0x600001f4c030
0x600001f4c031
0x600001f4c032
0x600001f4c033
0x600001f4c034
0x600001f4c035
0x600001f4c036
0x600001f4c037
0x600001f4c038
0x600001f4c039

The emplace_back member function of std::vector may be used to construct objects in-place in a vector.

In the following we construct 5 temporaries which we can see reuse the same memory, and then we use push_back to place them in the vector.

Then we use emplace_back to construct five objects directly in the vector.

We can see that the memory address printed when the objects are cerated are the same as those printed later.

Note: vectors may reallocate and move their contents if size exceeds capacity. To avoid this in this case I've reserved a capacity of 10 for the vector v.

#include <iostream>                                                                                                                                                                                            
#include <vector>

struct myclass {
    myclass() {
        std::cout << this << std::endl;
    }

    void print() const { 
        std::cout << this << std::endl; 
    }
};

int main() {
    std::vector<myclass> v;

    v.reserve(10);

    for (uint32_t i = 0; i < 5; i++)
        v.push_back(myclass());

    for (uint32_t i = 0; i < 5; i++) 
        v.emplace_back();

    std::cout << "\n";

    for (auto &x : v) 
        x.print();
        
    return 0;
}

Output:

0x7ff7bf57b598
0x7ff7bf57b598
0x7ff7bf57b598
0x7ff7bf57b598
0x7ff7bf57b598
0x600002610035
0x600002610036
0x600002610037
0x600002610038
0x600002610039

0x600002610030
0x600002610031
0x600002610032
0x600002610033
0x600002610034
0x600002610035
0x600002610036
0x600002610037
0x600002610038
0x600002610039
Convertiplane answered 18/12, 2023 at 22:40 Comment(5)
0x7ff... is near the top of the low half of 48-bit virtual address-space on x86-64; as expected from the code, this is a stack address for the temporary that only exists in automatic storage (like for named local variables). 0x60000... is far away, not a stack address. Which makes sense because std::vector uses dynamic storage (via new) to make space for the objects.Cellulose
Also interesting is that even though the class does not declare any storage, each object is 1 byte apart in memory, and sizeof(myclass) reports 1. This might seem odd, but it makes perfect sense when you consider that the this pointer must have a unique value for each created object: the easiest way to satisfy this rule is to ensure that you can never have a object that is zero bytes in length.Ulcerous
Perhaps worth mentioning is std::vector::emplace_back() which actually does construct the object in-place in the vector.Kenny
@Ulcerous well not for "each created object" as we can see in the OP—rather, for each object in an array, so that array[n+1] would unambiguously point to the next element.Edmonds
@Edmonds - okay, “created” may be vague for some readers, so, I will restate it like this: no two objects that both exist in the current runtime state can share a this value. However, if you create, take a copy and then dispose as the OP did, then the next time you create, you will most likely re-use the same memory as before, so the this value of each temporary will be the same. That doesn’t violate the rule, because only one existed at any time. Each of those temporaries is still 1 byte in length, and the point I was making was that even “empty” C++ objects are at least 1 byte in size.Ulcerous

© 2022 - 2024 — McMap. All rights reserved.