c++ smart pointer question

Started by
7 comments, last by Misantes 9 years, 7 months ago

Totally a minor problem here, but I thought I would ask for help in explaining smart pointers a little.

I'm trying to get a better handle on pointers and c++11 smart pointers, but I think I'm misunderstanding smart pointers a little. Most of the references I've come across haven't been of too much use, as they're mostly geared toward a more experienced programmer, I feel. I understand the auto-deletion of unique_ptrs when they go out of scope, and why that's desirable. But, I'm having quite a bit of difficulty using them the same way as a normal pointer.

Typically, one could make a pointer and have it equal a reference object like:


int firstNumber = 10;
int *pointer1;

pointer1 = &firstNumber;//*pointer1 is the value located at memory of firstNumber.

I've been trying to get the same functionality with std::unique_ptr and am failing to really understand how to initialize them and use them.

Trying:


int firstNumber = 10;
std::unique_ptr < int > pointer1;
pointer1 = &firstNumber;

results in an error. |459|error: no match for ‘operator=’ (operand types are ‘std::unique_ptr<int>’ and ‘int*’)|

Fair enough. But, if I change that to:


int firstNumber = 10;
//std::unique_ptr< int > pointer1(new int(&firstNumber));//results in error
//std::unique_ptr< int > pointer1(new int(firstNumber));//doesn't seem to reference memory at firstNumber
std::unique_ptr< int > pointer1(new int(10));

pointer1 = &firstNumber;//results in same error,

*pointer1 = firstNumber//doesn't reference memory at firstNumber.
*pointer1 = &firstNumber//results in error

I've tried:


int firstNumber = 10;
std::unique_ptr < int > pointer1(new int(10));
std::unique_ptr < int > pointer2(new int(20));

*pointer2 = firstNumber;
pointer1 = pointer2;//results in error
pointer1 = *pointer2;//does change pointer 1 but doesn't seem to reference firstNumber, nor memory at pointer2

I'm feeling a little on the dumb side for not grasping this, but I'm obviously missing something(or maybe many things).

I'm apparently doing something wrong, but it feels like the unique_ptr isn't actually behaving like a pointer at all, functionality-wise.

Anyhow, any advice or help in understanding the functionality of unique_ptrs would be extremely appreciated. Feel absolutely free to dumb things down, I will not feel patronized or condescended to in the slightest :)

Thanks in advance, :D

Beginner here <- please take any opinions with grain of salt

Advertisement

First, smart pointers don't replace regular points. They provide a safer subset of regular pointer features with addition bonuses like keeping track of how many smart pointers are referring to the memory and knowing when to delete the memory.

Typically, one could make a pointer and have it equal a reference object like:


int firstNumber = 10;
int *pointer1;

pointer1 = &firstNumber;//*pointer1 is the value located at memory of firstNumber.
I've been trying to get the same functionality with std::unique_ptr and am failing to really understand how to initialize them and use them.

Objects on the stack, like 'firstNumber', automatically deallocate themselves when they go out of scope. You never* want to give an object created on the stack to a smart pointer, because after it goes out of scope, then the smart pointer will later go out of scope, and it'd get double-deleted and crash your program (if you're lucky). ohmy.png

*Almost never. 99.999% of the time.

You don't want to take smart pointers and just use them to wantonly replace existing pointers wholesale. That's not what they are for. Only when you are manually allocating dynamic memory, do you want to use smart pointers to automatically do the allocating (std::make_shared()) and de-allocating (when the pointer goes out of scope) for you. smile.png

When you aren't managing dynamic memory, you don't need smart pointers. References are preferred, and raw pointers otherwise*.

*Or std::reference_wrapper(), but I don't want to toss too many new classes at you at once.

When you do create dynamic memory (when you actually need dynamic allocation), you do it like this:


std::shared_ptr<MyClass> myPtr = std::make_shared<MyClass>("arguments to pass to MyClass's constructor");

Some general guidelines:

  • std::unique_ptr is if there is a sole owner/observer.
  • std::shared_ptr is if there are one or more owners, and one or more observers.
  • std::weak_ptr is for the non-owning observers of a shared_ptr's memory.
  • References are for variables where you know the lifetime of the variable will outlast the reference.
  • Raw pointers are for variables where you aren't owning the memory, and the memory will outlast the pointer, and you want the pointer to either change later in its lifetime or else be optional (i.e. nullptr).

There are other uses for things like raw pointers, but these are just an example of the most common cases.

The smart pointers are about managing the ownership of a resource. If you store an object within an std::unique_ptr, you're telling the smart pointer that it has exclusive ownership of the object and is responsible cleaning it up.

But the problem is that you're trying to hand it an object, an integer variable, with automatic storage to a smart pointer that is supposed to claim ownership of it. The integer variable is already exclusively owned and managed by the surrounding scope (function body, for loop, whatever scope it is defined in) and you cannot transfer that ownership to the smart pointer.

Smart pointers are not arbitrary replacements for naked pointers; they are tools for managing ownership of objects. If you're not allocating objects dynamically, you most likely don't need any smart pointer at all for the object because if it is not dynamic then it is probably statically allocated or has automatic storage.

First off, smart pointers are not meant for managing items on the stack. They are meant for managing items on the heap. They are designed to change


{
int *foo = new int(10);
delete foo;
}

into


{
  std::unique_ptr<int> foo(new int(10));
}

notice how unique pointer removes the need to remember the matching call to delete?

That also means that it's always going to delete it's input, so attempting to feed it a pointer off the stack will cause it to crash on destruction:


{
  int num = 5;
  std::unique_ptr<int> ptr1(&num);
} // <-- crash

Secondly, each smart pointer type has it's own semantics about how it manages memory. unique_ptr in particular is designed to give a single point of code ownership of an object on the heap. That means that a unique pointer must transfer ownership, and may not be a copyed. So if you did want to transfer ownership you need to use move semantics


std::unique_ptr<int> p1(new int(10));
std::unique_ptr<int> p2(std::move(p1));

which will result in p1 being empty, and p2 containing the integer valued 10.

Thanks for the lightning quick replies everyone. That clears up a ton of questions I had and answers my initial questions perfectly (though, I still have a lot of reading to do on the subject). I was definitely thinking of smart pointers simply as safer replacements to raw pointers.

I'm going to admit, I'm still a little fuzzy on when exactly I would need to use a pointer (of either sort). Even in my more complex games, with dozens of classes and vectors of classes interacting with each other, I seem to be able to work around them just fine. I'm mostly trying to learn more about them to figure out when exactly it would be appropriate and necessary (or even just more efficient) to use them. I'm happy enough to avoid them, but I'd prefer to avoid them knowing why I'm not using them, rather than out of ignorance (my current avoidance is more of a crutch than a purposeful use of the alternative methods).

My current reason for wanting to understand pointers a little more, is that in my current project, I have a vector of objects that are being displayed. I sort the objects for rendering as they're billboarded images. However, I need to go through the objects each turn when a button is pressed (ala the civilization games, where after giving orders to one object, the camera moves to the next player object to give orders to). But, having sorted these objects based on camera distance each iteration, it's difficult (for me or the program) to tell which object is which any longer without some pretty wonky workarounds. I'd prefer not to have two vectors, one for each player, as it makes sorting a little more complicated, though I'm sure still doable. I thought perhaps this would be an appropriate use for pointers, since regardless of where in the vector the object was moved to, the values should still be the same when referenced (i could definitely be wrong here, I haven't tested it. It may be that when sorting a vector, it just moves the values and not the objects). I can totally find a workaround for this without using pointers(my current method is to just use a second vector of ints, and when sorting, just sort that vector according to the distance of the actual vector of objects, then when rendering, render the actual vector of objects in the order of the sorted second vector. This probably isnt' ideal :P), I just thought perhaps it would be simpler and clearer to use them here. If you have any advice for this current little problem, I'd be happy to hear it, but this is definitely a second question and I'm happy to make an additional thread if needed.

Though, don't feel obligated to answer any follow-up questions, there are plenty of other similar threads I can look up on that (though, of the ones I've read so far, I still seem a little confused on them). Thanks for the help everyone, I genuinely appreciate it! smile.png

Beginner here <- please take any opinions with grain of salt

Thanks for the lightning quick replies everyone. That clears up a ton of questions I had and answers my initial questions perfectly. I was definitely thinking of smart pointers simply as safer replacements to raw pointers.

They are safer replacement, but when there's no risk there's no reason to go safer. What they are not, however, are arbitrary replacements. That is, don't just replace your pointers, but replace them when needed to eliminate risk of resource leakage. Now, "when needed" is about learning about ownership in general and how the different smart pointers work. That comes with experience.

I'm going to admit, I'm still a little fuzzy on when exactly I would need to use a pointer (of either sort). Even in my more complex games, with dozens of classes and vectors of classes interacting with each other, I seem to be able to work around them just fine. I'm mostly trying to learn more about them to figure out when exactly it would be appropriate and necessary (or even just more efficient) to use them. I'm happy enough to avoid them, but I'd prefer to avoid them knowing why I'm not using them, rather than out of ignorance (my current avoidance is more of a crutch than a purposeful use of the alternative methods).

It is perfectly fine and possible to write complex software (with some definition of complex, of course) without explicitly touching dynamic allocations. You can do dynamic arrays with vectors for example, and the vector will safely handle its ownership by itself.

Once you start allocating "dumb" resources, for example allocating objects with new or some other function that gives you, the programmer, the responsibility of releasing said resource, you can/should look into smart pointers. But if there's no ownership you have to manage, or on other words; you're not given the responsibility to clean it up by yourself, then there's no reason for smart pointers either.

Take KulSeran's post above as an example. The first code box allocates a dumb resource and you have to call delete on the pointer. That's a perfect place for a smart pointer as shown in the second code box. The third code box doesn't allocate anything dynamically, the integer has automatic storage and nothing has to be cleaned up. There's no need for a smart pointer here because, as said, there's nothing to clean up.


(i could definitely be wrong here, I haven't tested it. It may be that when sorting a vector, it just moves the values and not the objects)

Yeah... That's totally wrong. C++, unlike other languages like Java has a really bare-bones notion of what an "object" is.


vector<int> numbers;
numbers.push_back(20);
numbers.push_back(10);

int *n1 = &numbers[0];
int *n2 = &numbers[1];

std::sort(numbers.begin(), numbers.end());
std::cout << *n1 << " " << *n2 << std::endl;

// NOTE: after this line, n1 and n2 may no longer point to valid memory!!!
numbers.push_back(30);

Pointers point to a memory locations, not "objects". In this case a vector has a pointer to a contiguous block of memory that it uses for storage. The pointers n1 and n2 point into that storage. Sorting the vector moves around values within that storage, and now your pointers are pointing to different numbers because that is the data now stored at that memory location. Then, to my final note, adding more objects to a vector can cause it to copy it's content to totally different location in memory, leaving n1 and n2 pointing into nothingness/undefined-bahavior land.

Speaking of pointers (and references) in general, when not pointing at dynamic memory:

Even in my more complex games, with dozens of classes and vectors of classes interacting with each other, I seem to be able to work around them just fine.
[...]
I'm happy enough to avoid them, but I'd prefer to avoid them knowing why I'm not using them

Pointers shouldn't be avoided, but they shouldn't be used just for the sake of using them.

By default, a variable shouldn't be a pointer; it's not a matter of knowing when not to use them, but knowing when to use them.

I'm going to admit, I'm still a little fuzzy on when exactly I would need to use a pointer (of either sort).
[...]
I'm mostly trying to learn more about them to figure out when exactly it would be appropriate and necessary (or even just more efficient) to use them.


Here are three situations where you might want a reference or pointer:
Usage #1: You use a pointer or reference when you need more than one variable to point to the same data (either for read-only or read-write usage), rather than trying to keep two or more variables in-sync with each other.
Usage #2: You also use a pointer/reference when you need to pass around a large variable to functions, but you don't want for to copy that memory when you aren't actually needing a copy.
Usage #3: Another use of pointers and references is when you want a function to modify an existing variable.

(There are other situations as well, these are just three common ones)


struct ButtonAppearance
{
   Font font; //Font for the button.
   Texture background; //Appearance of the button.
   Color defaultFontColor;
};

class Button
{
public:
   Button(const ButtonAppearance &sharedButtonData)
      : sharedButtonData(sharedButtonData)
   {    }
   
   //We don't want to COPY the screen, we want to modify the original screen. (See Usage #3 above)
   //So we use a non-const reference.
   void DrawTo(Screen &screen) const
   {
      //Draw the button background on the screen.
      DrawTexture(screen, sharedButtonData.background);
      
      //Draw the button text, using the shared font, but the local text color and the local text.
      DrawText(screen, sharedButtonData.font, textColor, text);
   }

private:
    const ButtonAppearance &sharedButtonData; //Const references are read-only.
    Color textColor;
    std::string text;
};

//We don't want to modify OR copy a huge number of buttons. We want to draw them in a way that doesn't
//change the buttons themselves (only changes the screen). So we pass the buttons by const reference. (See Usage #2)
void DrawAllTheButtons(const std::vector<Button> &buttons, Screen &screen)
{
   for(...every button...)
   {
      buttons[0].DrawTo(screen); //We don't want to copy the screen, only modify the original screen.
   }
}

int main()
{
   ButtonAppearance buttonAppearance;

   std::vector<Button> alotOfButtons;
   for(...however many...)
   {
      //Every button is sharing the 'buttonAppearance' data, instead of copying it. (See Usage #1)
      Button newButton(buttonAppearance);
      alotOfButtons.push_back(newButton);
   }

   Screen screen;
   DrawAllTheButtons(alotOfButtons, screen);
   
   return 0;
}

Passing by reference or pointer is roughly the same in size and performance as passing a large integer. This is instead of passing thousands of bytes of data for large arrays of data.

[Edit:] I should mention, smart pointers aren't just for extra safety (preventing human programmers from making really common mistakes), they also also for self-documenting code. Smart pointers show the intent of the code, both to yourself when you need to read or modify the code a few months later, and to others reading your code. They document the intent when used in a consistent way; they don't actually enforce intent 100%, because, while they use compile-time errors to say, "hey, you are accidentally violating your agreements to be read-only (const) or to be the sole-owner (unique_ptr)", you can actually tell the compiler to allow you to violate the intent (by, for example, removing 'const' from a variable in certain functions, or by manipulating the unique_ptr to take ownership from it).

So smart pointers are also about providing tools for programmers to make their code be less mistake-prone by allowing programmers to make promises that, yes, can be broken, but that are less likely to be broken by accident. These promises can be read by the compiler so it can make better optimizations, as well as read and comprehended by other programmers so they can make more informed decisions when editing code.

Smart pointers show greater intent (reveal more information) than raw pointers do.


Pointers point to a memory locations, not "objects". In this case a vector has a pointer to a contiguous block of memory that it uses for storage. The pointers n1 and n2 point into that storage. Sorting the vector moves around values within that storage, and now your pointers are pointing to different numbers because that is the data now stored at that memory location. Then, to my final note, adding more objects to a vector can cause it to copy it's content to totally different location in memory, leaving n1 and n2 pointing into nothingness/undefined-bahavior land.

Ah, I had a feeling it may work this way. I just wasn't certain if the memory location would be moved with the sorting or not. You explanation clears that up perfectly.

edit* for whatever reason, the selective quote makes your quote empty, and posts it as my text :P iono. Here's your quote:

"Here are three situations where you might want a reference or pointer:
Usage #1: You use a pointer or reference when you need more than one variable to point to the same data (either for read-only or read-write usage), rather than trying to keep two or more variables in-sync with each other.
Usage #2: You also use a pointer/reference when you need to pass around a large variable to functions, but you don't want for to copy that memory when you aren't actually needing a copy.
Usage #3: Another use of pointers and references is when you want a function to modify an existing variable."

//end Servant's quote :P


Ok. I use the second and third examples pretty liberally already(at least the reference side of things). I guess I just wasn't semantically considering that the use of "pointers." The first is one I don't often use, and probably the use I should look into understanding better.

These responses are leading me to think that perhaps I'm overcomplicating things, and feeling as though I probably ought to be using pointers more often (or at least missing out by not using them), when in fact, I'm already using them (more or less) correctly as is. I definitely have some reading to do (especially the whole stack/heap thing. I have a rudimentary understanding of the difference, but don't really understand the details of them).

Thanks for the responses everyone. smile.png I definitely now still have questions about sorting multiple vectors together, but I'll make a new thread for it.

Beginner here <- please take any opinions with grain of salt

This topic is closed to new replies.

Advertisement