1

The difference between smart pointers and raw pointers has been discussed before (e.g. When should I use raw pointers over smart pointers?), but I can't quite get the answer to this question from the material I have been reading the last day or so.

I have a class A that has a pointer int* a to some data. In the context I am thinking about, the value that a points to might be used somewhere else in the program so A does not have ownership over a, it just refers to it. For example, a house exists (e.g. int h) and a person (i.e. class) has a reference to their house (e.g. int* my_h).

The first way I handled this was without using smart pointers, but I am curious about the benefits of using smart pointers in this example. I suspect there are not many because ownership really isn't much of an issue and I am not calling new and delete.

The example with raw pointers:

#include<iostream>

class A{

public:
  A(int a_val){ 
    std::cout << "Creating A instance ";
    a = &a_val;
    std::cout << "with a = " << *a << std::endl;
  };

private:
  int* a;
};

int main()
{
  int x = 5; 
  std::cout << "x is " << x << std::endl;
  A a(x); 
  return 0;
}

Here, a has the raw pointer int* a, which is assigned to &x in main().

The example with smart pointers (a unique_ptr):

#include<memory>
#include<iostream>

class A{

public:
  A(std::unique_ptr<int> a_val){ 
    std::cout << "Creating A instance ";
    a = std::move(a_val);
    std::cout << "with a = " << *a << std::endl;
  };

private:
  std::unique_ptr<int> a;
};

int main()
{
  std::unique_ptr<int> x = std::make_unique<int> (5);//int x = 5;
  std::cout << "x is " << *x << std::endl;
  A a(std::move(x));
  return 0;
}

The use of unique_ptr seems overkill here to me, and doesn't benefit readability or performance. Is that correct?

EDIT

As pointed out (...) in the comments, there were a number of problems with the original example. The raw pointer example should be:

#include<iostream>

class A{

public:
  A(int* a_val){ 
    std::cout << "Creating A instance ";
    a = a_val;
    std::cout << "with a = " << *a << std::endl;
  };

private:
  int* a;
};

int main()
{
  int x = 5; 
  std::cout << "x is " << x << std::endl;
  A a(&x); 
  return 0;
}

and the smart pointer example should perhaps use a shared_ptr.

To be more specific, I am interested in cases where this scales up to large numbers of instances of classes, which have pointers (or vectors of pointers) to data structures defined elsewhere. For instance, in agent based models, agents sometimes need to 'find' another agent, and thus a vector of pointers could (in my understanding) be used to refer to which other agents one particular agents should 'know about'. The agents themselves will be created outside of the agent class.

user_15
  • 151
  • 9
  • 8
    One difference is that your raw pointer version doesn't work. You assign a pointer to a temporary variable, which dies as soon as constructor is done. Which highlights a very important difference between raw and smart pointers - *smart pointers prevent you from most of such mistakes*. – Yksisarvinen Aug 15 '19 at 18:42
  • Indeed, YKsisarvinen is quite right! You should declare the constructor as A(int* a_val) and have a = a_val in the body; construct from main with A a(&x). – Adrian Mole Aug 15 '19 at 18:54
  • Only slightly related, I'd also move that smart pointer initialization to where it belongs; a member initialization list for the ctor. Right now you're needlessly using default-construction and move-assignment. Fit and form, I guess. – WhozCraig Aug 15 '19 at 18:55
  • 2
    @Yksisarvinen That is not a reason to use smart pointers. The mistake OP made has nothing to do with that. – Acorn Aug 15 '19 at 18:57
  • One question every programmer has to ask him/herself before dereferencing a pointer is, “how do I know this pointer is valid, ie that dereferencing it won’t invoke undefined behavior?” With raw pointers, you may have to examine the entire program’s behavior to convince yourself it’s guaranteed to be valid; with a smart pointer, you just have to check that if it’s non-null. – Jeremy Friesner Aug 15 '19 at 18:57
  • After changing `A(int a_val)` to `A(int &a_val)` so that the example doesn't leave a dangling pointer (no need for a pointer at this point, just a reference), `main` owns `int x`. It also owns `A a` and the order of definition guarantees that `x` will outlive `a`. The validity is easy to check and the `unique_ptr` is not required here. But does that scale to several functions and a few hundred lines of code? Maybe. Does it scale to a thousands of functions across hundreds of files and a million lines of code? Probably not. – user4581301 Aug 15 '19 at 19:01
  • @Yksisarvinen Thanks! I didn't catch that. – user_15 Aug 15 '19 at 19:02
  • @NikosC. Pointers do not imply using exceptions, much less `try catch` blocks... – Acorn Aug 15 '19 at 19:09
  • I will edit the question to fix your raw pointer example. If you dont like it just roll back... – 463035818_is_not_an_ai Aug 15 '19 at 19:09
  • i asked a question not that different from this one not too long time ago. I think one obstacle one has to pass when being new to smart pointers is to realize that it isnt simply about replacing all raw pointers with smart ones. smart pointers dont relieve you of caring about ownership (ie who deletes the instance), to the contrary, they just give you a way to model ownership in a proper way. – 463035818_is_not_an_ai Aug 15 '19 at 19:14
  • @NikosC. I understood, but I am saying that does not make sense. Maybe you are trying to say something else. As I read it, you claim smart pointers solve X (putting `try` everywhere), but X is not a problem of pointers to begin with. – Acorn Aug 15 '19 at 19:15
  • note that your raw pointer example is broken. The method should take a pointer not a value, otherwise the pointer member is a dangling pointer, I had already fixed it in your question – 463035818_is_not_an_ai Aug 15 '19 at 19:17
  • @formerlyknownas_463035818 I already fixed the code in my edit...I think it's okay now? – user_15 Aug 15 '19 at 19:18
  • `A(int* a_val)` Works, but could be done with a reference for added guarantees. For example it takes deliberate malfeasance to pass a null reference. Accidental null pointers... Those happen every day. – user4581301 Aug 15 '19 at 20:44

4 Answers4

0

I am curious about the benefits of using smart pointers in this example

Depends on the smart pointer you are talking about:

  • unique_ptr: you cannot use this one for your use case, because that implies you own the object.
  • shared_ptr: this would work, but you would need to use shared_ptr everywhere and you would tie its lifetime to your class.
  • weak_ptr: same as above, but this would not tie the lifetime (it can be destroyed by others meanwhile).
  • Some other non-standard smart pointer: depends.

In general: if you only need to keep a reference to an object, use a reference or a pointer. Of course, you will have to ensure the reference/pointer remains valid, which can be trivial or a nightmare, depending on the design of your program.

Acorn
  • 24,970
  • 5
  • 40
  • 69
  • concerning ownership the two versions of OPs code are quite different. In the one with the `unique_ptr` the class `A` can be said to own the object, it takes some input value and creates a new isntance from that and is responsible for managing its lifetime. The example with raw pointer is just too broken to even talk about ownership – 463035818_is_not_an_ai Aug 15 '19 at 19:03
  • @formerlyknownas_463035818 I know, but when code and question disagree, you have to look at the question, not at the code. The question, in this case, clearly explains that OP wants a reference to some object without tying the lifetime. – Acorn Aug 15 '19 at 19:05
  • imho the code is so much broken that an answer should at least mention that it is a dangling pointer – 463035818_is_not_an_ai Aug 15 '19 at 19:06
  • 1
    @formerlyknownas_463035818 I don't agree. That should be a comment or an edit on the question. Otherwise, the answer becomes out of sync and/or invalid as soon as someone fixes the mistake. – Acorn Aug 15 '19 at 19:07
  • 1
    ok point taken. I edited the question and left a comment to OP in case they arent happy with the edit – 463035818_is_not_an_ai Aug 15 '19 at 19:10
  • @formerlyknownas_463035818 Thanks for doing it! – Acorn Aug 15 '19 at 19:15
  • meh my edit got overwritten by an edit of OP, I am out :P – 463035818_is_not_an_ai Aug 15 '19 at 19:16
  • Re "keep a reference to an object" I think you should include `weak_ptr` in the list of recommendations, that's exactly what it's for. – Mark Ransom Aug 15 '19 at 21:36
  • @MarkRansom `weak_ptr` is already listed, not sure what do you mean. Regardless, `weak_ptr` is not meant for "keeping a reference to an object". – Acorn Aug 15 '19 at 21:44
  • What do you mean? It's for holding a reference to an object whose lifetime is controlled by someone else. – Mark Ransom Aug 15 '19 at 21:46
  • @MarkRansom It is not. It is tied to a `shared_ptr`, which is a pretty big constraint. – Acorn Aug 15 '19 at 21:47
  • Then call out the constraint. It's still extremely useful. – Mark Ransom Aug 15 '19 at 21:49
  • @MarkRansom No, because the question isn't about that. And no, it is not "extremely useful". In fact, `shared_ptr` (and therefore `weak_ptr`) should be avoided unless there is no better solution to a problem. – Acorn Aug 15 '19 at 21:56
0

As was pointed out by @YKsisarvinen in a comment,

One difference is that your raw pointer version doesn't work. You assign a pointer to a temporary variable, which dies as soon as constructor is done. Which highlights a very important difference between raw and smart pointers - smart pointers prevent you from most of such mistakes.

One possible approach is to store a reference to the object that is owned by somebody else.

struct A
{
  A(int& a_val) : a(a_val) {}
  int& a;
};

However, that would prevent you from being able to assign objects.

int i = 10;
int j = 20;
A a1(i);
A a2(j);
a1 = a2;        // This will be an error.

You can use std::reference_wrapper to overcome that barrier.

struct A
{
  A(int& a_val) : a(a_val) {}
  std::refernce_wrapper<int> a;
};
R Sahu
  • 204,454
  • 14
  • 159
  • 270
0

The main question is: who's owning the pointer. In this example:

int main()
{
  std::unique_ptr<int> x = std::make_unique<int> (5);//int x = 5;
  std::cout << "x is " << *x << std::endl;
  A a(std::move(x));
  return 0;
}

It's just the same.

But if you do something like:

A funct(int n)
{
  std::unique_ptr<int> x = std::make_unique<int> (n);
  std::cout << "x is " << *x << std::endl;
  A a(x.get());
  return a;
}

Then you NEED to transfer the ownership, because as soon as you leave funct, x goes out of scope and gets destroyed. And the A object that you return now points to garbage. So you'll do:

A funct(int n)
{
  std::unique_ptr<int> x = std::make_unique<int> (n);
  std::cout << "x is " << *x << std::endl;
  return A(std::move(x));
}

So, the A returned now owns the pointer and will destroy it when itself gets destroyed.

Mirko
  • 1,043
  • 6
  • 12
0

Even before jumping into the benefits of Smart Pointers, I would recommend understanding why are they even needed. First and foremost, smart pointers were never meant to replace the use of all Raw pointers. They were designed with a very very specific purpose: Memory management i.e. cleaning up/freeing dynamically allocated memory.

Now lets say we have a simple function as follows:

 1. void func()
 2. {
 3.    SomeResource* raw_sr = new SomeResource();  //--->Memory allocated here.
 4.    .
 5.    .
 6.    if(someCondition==false)
 7.       return;
 8.
 9.    delete raw_sr;
10.       return;
11.}

The above function can terminate in 2 ways: 1. Returning from Line 10. -->Normal termination 2. Returning from Line 7. -->Termination under some error condition

In the first situation we cleaned up the memory. But for situation 2, we have a potential memory leak as we did not free the raw_sr.

So apparently, there is only one place where the memory was not deleted. We can add an explicit code there to free up the raw_sr memory.

But the point is, there can be a number of situations in the code, where this can happen and forget to free up the memory. It can also be while handling exceptions.

But what if, under all terminating conditions we are guaranteed that the memory will be freed up ?

There is only one function that is called under all such circumstances: the destructor of a static object.(Not dynamic - very imp.)

So the idea is to give the responsibility of holding a pointer to a specific class whose job will be to free up the memory held by the pointer when its destructor is called. This class is called a Smart Pointer.

So now your code would look like this:

 1. void func()
 2. {
 3.    SmartPointer<SomeResource> smart_sr(new SomeResource());  //--->Memory allocated here
 4.    .                                                          // but passed onto the smart_sr obj.
 5.    .
 6.    if(someCondition==false)
 7.       return;
 8.
 9.    return;
10.}

Now under all circumstances, be it on line no 7 or 10, the destructor of smart_sr will be called and that will free up the memory held for SomeResource.

This is the primary and fundamental understanding of a Smart Pointer.

Hence, usage of a smart pointer depends on its usage context.

Now based n your example, Class A represents a Person that holds int* my_house. Now lets say you are not the only person staying in this house. You are staying with your wife.

From a logical perspective your object and your wife's object should point to the same House pointer. So your object alone CANNOT be the owner of this house's pointer. Because then your Wife's object wont be able to refer to it. So std::unique_ptr cannot be used for holding the house pointer.

Well you guys share the home ..right ? Hence, std::shared_ptr, will make sense. Now lets say, your wife left, the home(for some reason) so she gives up the pointer. Will the house be returned back to its owner ? No. You are still staying there. Subsequently, you move out too after a few days, then you too will give up this pointer. And because you were the last person staying in it, the memory holding this house will finally be freed.

Hope this clears your doubt and puts you in the right direction to understanding smart pointers.