Jump to content
  • Advertisement
Sign in to follow this  
newtechnology

Confusion with smart pointers

This topic is 1001 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

So, I read that you cannot have two std::unique_ptr point to same object. If you want to do that, you need to use std::move and that'll transfer ownership of it. So, I tried to do that with this code.


int main()
{
 
std::unique_ptr<Random> SP(new Random());
 
printf("SP : %X\n", SP.get());
 
std::unique_ptr<Random> SP2(std::move(SP));
 
printf("SP : %X\n", SP.get());
printf("SP2 : %X\n", SP.get());
 
SP->SomeFunc();
SP2->SomeFunc();
 
system("PAUSE>NUL");
 
return 0;
}
 
and got this output:
tHARAaU.png
 
Previously, SP was pointing to some address (A6A748) and now it points to 0 because it has transferred it's ownership to SP2 (So I assume it's nullptr?)
But the confusion I get is that this now points to nullptr (that is what I think) yet I am able to call SP->SomeFunc();.
Shouldn't I get an error because now SP doesn't point to anything cause it has moved it's ownership to SP2 ?
 
 
EDIT:
Here's the code for Random class if you're wondering what SP->SomeFunc() and SP2->SomeFunc() do

class Random
{
public:
Random();
~Random();
 
void SomeFunc();
};
 
void Random::SomeFunc()
{
printf("SomeFunc() called (Address: %X)\n", this);
}
 
Random::Random()
{
printf("Constructor called!\n");
}
 
Random::~Random()
{
printf("Destructor called!\n");
}
Edited by newtechnology

Share this post


Link to post
Share on other sites
Advertisement

c++ is low level - there's no fancy-shmancy runtime that would check for null before calling a method. If you look at the assembly generated for your code (always educational if you want to understand how things work under the hood), you'll see why you are getting those results.

Share this post


Link to post
Share on other sites

You would get access violations if you were trying to access member variables in those functions on very low address. Like access violation at 0x00000008 for example, usually when seeing these low address values in access violations it indicates that your object access has been cleared. Because members are usually accessed by adding the field offset to the object memory address.

Share this post


Link to post
Share on other sites

So, I read that you cannot have two std::unique_ptr point to same object.

 

Technically you can:

#include <memory>

int main()
{
   int* foo = new int;
   std::unique_ptr<int> fooPtr1(foo);
   std::unique_ptr<int> fooPtr2(foo); // Double-delete bug!
}

So always be careful (and consistent!) with how you mix and match your smart pointers with your regular pointers!

Share this post


Link to post
Share on other sites

 

[EDIT] Adding this note to be clear as to what the others are saying -- this is undefined behavior. However, it's likely to work as and why I describe below, and as others have alluded to because it just falls out of how a compiler does dispatch. The compiler isn't doing any special work to make this work, and doesn't care that it does. If a compiler vendor found a better way to do dispatch that broke the behavior you're seeing they would be free to do so and still maintain their standards conformance. In practice, they might choose not to do so, or might put one or the other behavior behind a compiler switch precisely because there probably exists code that relies on this behavior even though it shouldn't, strictly speaking.

 

In SomeFunc, you're not actually dereferencing 'this' at any point. You examine its value (to print it) and that's fine, you're allowed to look at a null pointer -- its only dereferencing it that's undefined.

 

Functions themselves aren't part of an object instance and so don't require a dereference to dispatch (unless they're virtual) -- they exist in your executable always (presuming they're called at least once, or you've instructed the toolchain to leave them even if not). Calling a virtual function would change that, because there's an indirection happening through a virtual function table pointer that's stored in the object (hence you'd have to dereference the object first) -- this, I believe, is only a problem when the the virtual member function is called through a pointer to base class though; if the compiler can statically know which virtual function to call I'd imagine it elides the indirection (but I'm not 100% certain).

 

Also:

 

printf("SP : %X\n", SP.get());
printf("SP2 : %X\n", SP.get());     <-- You're not printing SP2.get() here.

 

Nice catch. I fixed it and this is the new output. (Still reading your people's posts, will reply when I have questions again.)
vUeZtvj.png

 

EDIT: There's something going on.
 

After using *this instead of this here,

 
void Random::SomeFunc()
{
printf("SomeFunc() called (Address: %X)\n", *this);
}

The program crashes at SP->SomeFunc(); but not at SP2->SomeFunc(); !

 

So, this works fine

 
int main()
{
std::unique_ptr<Random> SP(new Random());
 
printf("SP : %X\n", SP.get());
 
std::unique_ptr<Random> SP2(std::move(SP));
 
printf("SP : %X\n", SP.get());
printf("SP2 : %X\n", SP2.get());
 
//SP COMMENTED OUT!
//SP->SomeFunc();
SP2->SomeFunc();
 
system("PAUSE>NUL");
 
return 0;
}

but this doesn't (Crashes at SP->SomeFunc())

 

int main()
{
std::unique_ptr<Random> SP(new Random());
 
printf("SP : %X\n", SP.get());
 
std::unique_ptr<Random> SP2(std::move(SP));
 
printf("SP : %X\n", SP.get());
printf("SP2 : %X\n", SP2.get());
 
SP->SomeFunc();
SP2->SomeFunc();
 
system("PAUSE>NUL");
 
return 0;
}

Here is the access violating. It says Reading 0x000000, so it means now SP points to nullptr, right?

Why didn't that happen with this pointer? Why did I have to use *this (dereference) ?

 

QkdZQ6f.png

Edited by newtechnology

Share this post


Link to post
Share on other sites

Why didn't that happen with this pointer? Why did I have to use *this (dereference) ?

 

This has already been answered. Reading the value of a nullpointer is ok. Using a nullpointer, which includes dereferencing it, isn't.

int* pValue = nullptr;

print(pValue); // works: prints the value of the pointer - whether it is 0x000000, 0xEF9283 doesn't matter

print(*pValue); // fails: tries to print the value of the thing the pointer is pointing to - and 0x00000 doesn't point to anything

Doesn't matter whether its "this" or  a regular pointer, whether its a class or a builtin type...

Edited by Juliean

Share this post


Link to post
Share on other sites
Also, please keep in mind that doing anything other than destruction with a moved-from type is undefined behavior.

As you discovered, your implementation of unique_ptr decided to set itself to null, but there is no rule that requires a moved-from unique_ptr to be null, simply that it must be destroyable. Some fancy version of the standard library may decide to set a "owned" flag to false and leave the pointer alone instead. (I have no idea why they'd do something like that, but the standard does not forbid it)

Heck, a really smart compiler may decide that once you've called std::move on an object without a destructor it can re-use the stack space for something else.


Edited: As corrected by BitMaster - valid but unspecified state for standard library objects, not undefined behavior.

I will still state that I would question any code review containing code that re-used a moved from variable, simply because of the potential confusion. Edited by SmkViper

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!