0

This is a great answer about smart pointers, such as unique pointers: What is a smart pointer and when should I use one?.

Here is an example they provide as the simplest use of a unique pointer:

void f()
{
    {
       std::unique_ptr<MyObject> ptr(new MyObject(my_constructor_param));
       ptr->DoSomethingUseful();
    } // ptr goes out of scope -- 
      // the MyObject is automatically destroyed.

    // ptr->Oops(); // Compile error: "ptr" not defined
                    // since it is no longer in scope.
}

However, this begs the question: in cases such as this, where the goal is to simply delete the object (free the memory) the unique pointer points to when it goes out of scope, why not just put the whole object on the stack instead, like this??

void f()
{
    {
       MyObject myobj(my_constructor_param);
       myobj.DoSomethingUseful();
    } // myobj goes out of scope -- 
      // and is automatically destroyed.

    // myobj.Oops(); // Compile error: "myobj" not defined
                     // since it is no longer in scope.
}

It seems to me the only logic can be that some objects are so stinking large they may overflow the stack, as stacks seem to be limited to a few dozen KB to a few MB (C/C++ maximum stack size of program), whereas a heap can be hundreds of GB!

What's the logic? Give me some insight here into this seemingly unnecessary use case of the unique pointer. What am I missing?

Related:

  1. "Another feature of the stack to keep in mind, is that there is a limit (varies with OS) on the size of variables that can be stored on the stack. This is not the case for variables allocated on the heap." (https://gribblelab.org/CBootCamp/7_Memory_Stack_vs_Heap.html)
trincot
  • 317,000
  • 35
  • 244
  • 286
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
  • 5
    That isn't a good use case for `unique_ptr`. Consider the case where you want to return a dynamically allocated object or if you want to store a dynamically allocated object in a container instead. It's specially useful with polymorphic types, which I guess is a use case for using it as a stack object. – François Andrieux Feb 26 '20 at 19:45
  • 1
    A good use case for ```unique_ptr``` would be if it is not defined at compile time if and how many objects you want to allocate. – BlueTune Feb 26 '20 at 19:48
  • It makes for a stupidly simple example. Sometime examples can be too stupid. – user4581301 Feb 26 '20 at 19:50
  • 1
    in defense of the answer you copied from, the example is to demonstrate how smart pointers work, not to demonstrate a typical use case. Read the beginning "Smart pointers should be preferred over raw pointers. If you feel you need to use pointers (first consider if you really do), you would normally want to use a smart pointer as this can alleviate many of the problems with raw pointers, mainly forgetting to delete the object and leaking memory." so the premise is that you already made the decision that you need a smart pointer instead of a raw pointer – 463035818_is_not_an_ai Feb 26 '20 at 20:02
  • 1
    btw `MyObject() myobj;` is a typo, no? – 463035818_is_not_an_ai Feb 26 '20 at 20:04
  • 1
    on a second thought, the question to the asnwer, is "when should I use a smart pointer?", so yes reallly not the best example – 463035818_is_not_an_ai Feb 26 '20 at 20:05
  • I would also argue that your example for using stack is not good. One should instead use an unnamed temporary. `MyObject{}.doSomethingUseful();` – n314159 Feb 26 '20 at 20:41
  • @idclev463035818, yes, it's a typo: I corrected it from `MyObject() myobj;` to `MyObject myobj();`. – Gabriel Staples Feb 26 '20 at 21:20
  • 1
    @GabrielStaples `MyObject myobj();` is a function declaration, not a variable declaration. Get rid of the parenthesis altogether: `MyObject myobj;` – Remy Lebeau Feb 26 '20 at 21:23
  • @RemyLebeau, you are right, but I was trying to keep my example in-line with the same pseudo-syntax as the original: ie: `std::unique_ptr ptr(new MyObject());` in place of `std::unique_ptr ptr(new MyObject);`, to show there would be parenthesis there if you were passing in constructor parameters, as those parenthesis should be removed too if no constructor parameters are passed in, no? – Gabriel Staples Feb 26 '20 at 21:27
  • It certainly creates confusion, so I've changed in the 1st example: `std::unique_ptr ptr(new MyObject());` (which is also technically wrong, no?) to `std::unique_ptr ptr(new MyObject(my_constructor_param));`, and I've changed in the 2nd example: `MyObject myobj();` to `MyObject myobj(my_constructor_param);`. – Gabriel Staples Feb 26 '20 at 21:29

3 Answers3

5

While this is not a terrible useful example per-se, it becomes with some slight variations.

  1. Polymorphism
struct Base { void blah() { std::cout << "Base\n";}};
struct Derived : Base { void blah() {std::cout << "Derived\n";}};

void blub(bool which) { 
    std::unique_ptr<Base> ptr = which ? new Base : new Derived;
    ptr->blah();
}
  1. Non-standard deleter
{ 
    auto close = [] (FILE* fp) { fclose(fp);};
    std::unique_ptr<FILE, decltype(close)> ptr(fopen("name"), close);
} // closes file
  1. Dynamic Array (you can arguably use a vector)
{ 
    std:: unique_ptr<int[]> ptr( new int [n]); 
    // From C++14 on, prefer if it is no problem to value-initialize the array
    auto ptr = std::make_unique<int[]>(n);
    // From C++20 on, there is no reason for the naked new
    auto ptr = std::make_unique_for_overwrite<int[]>(n);
    // is equivalent to the first line
}

EDIT: This also reasonable for big arrays, even if the size is know at compile time. If the size of the array would be really big (and this should be very, very rare) and you really do not want to run the risk of a stack overflow, this would be a safer possibility. But very probably std::vector is the better alternative still. Only if your object type is neither moveable nor copyable, the vector will have problems (since it cannot reallocate itself if necessary, and hence you can call basically no modifying member function).

  1. Conditional creation of an object. (Only in C++11 and 14, after that use std::optional)
void blah (bool smth)
{
    std::unique_ptr<T> opt;
    if (smth) {
        opt = std::unique_ptr<T>(new T);
    }
}
n314159
  • 4,990
  • 1
  • 5
  • 20
  • 1
    Good examples, I would add one when you create object instance conditionally – Slava Feb 26 '20 at 20:19
  • Only in C++11 and 14. After that I would definitely use an optional. – n314159 Feb 26 '20 at 20:33
  • Great, just please fix syntax, `opt = new T;` won't compile, probably opt = std::make_unique()` but that C++14 only – Slava Feb 26 '20 at 20:43
  • `std::optional` isnt a replacement for smart pointers, because it stores the object always on the stack, no dynamic allocations. Once you use dynamic allocation it isnt a `std::optional` – 463035818_is_not_an_ai Feb 26 '20 at 21:22
  • I'm addition to your examples, my understanding that one will overflow the stack if statically allocating very large arrays of objects, but not for dynamically-allocated large arrays of objects, is still correct too, right? You didn't mention this in your answer. – Gabriel Staples Feb 13 '21 at 17:46
  • That is correct. But in most cases, you would probably use an `std::vector` for this use case, there are only some very minor cases in which one would prefer an `std::unique_ptr`. I will add that to the answer. One thing: You probably shouldn't use the wording `statically allocating` for this, since this can be confused with `static` storage duration. – n314159 Feb 14 '21 at 12:26
1

If you want to return a pointer to an object, then without smart pointers, we had to do this:

MyClass* someFunc() {
    MyClass* myObjPtr = new MyClass();
    .
    .
    return myObjPtr;
}

void otherFunc() {
    MyClass* myPtr = someFunc();
    .
    .
    delete myPtr;  // delete explicitely before return.
    return;
}

Instead if we use smart pointers, we can do this:

typedef std::shared_ptr<MyClass> MyClassPtr;

MyClassPtr someFunc() {
    MyClassPtr myObjPtr(new MyClass());
    .
    .
    return myObjPtr;
}

void otherFunc() {
    MyClassPtr myPtr = someFunc();
    .
    .
    return;  // when smart pointer goes out of scope,
    // the heap allocated object which it contains is deallocated properly.
}
Sourav Kannantha B
  • 2,860
  • 1
  • 11
  • 35
0

Size is not the primary concern, although it may be important if you have recursion, for example (I saw a library allocating 64 KiB buffer on stack, in a recursive function. But Musl provides 128 KiB stacks [to make threads lightweight], so...) But the object may be polymorphic, and not even created “right there” but rather returned from some function (as a pointer); unique_ptr may be handy to store that.

Besides that, unique_ptr (unlike auto_ptr AFAIK) is not restricted to on-stack usage. It can be a class member as well. Also it is not required to actually store anything, you may assign and reset it at any time.

Moreover, it is not restricted to C++ classes, you can store anything requiring cleanup there, like file descriptor or FILE*, for example.

numzero
  • 2,009
  • 6
  • 6