Search code examples
c++std-function

When the object of std::function is destroyed?


When the object of std::function is destroyed? Why the pointer to the object of std::function is still valid when the variable fn1 is out of the scope(you see the code snippet works well, http://cpp.sh/6nnd4)?

// function example
#include <iostream>     // std::cout
#include <functional>   // std::function, std::negate

// a function:
int half(int x) {return x/2;}

int main () {
    std::function<int(int)> *pFun;
    
    {
        std::function<int(int)> fn1 = half;                    // function
     
         pFun= &fn1;   
         std::cout << "fn1(60): " << (*pFun)(60) << '\n';
    }

    std::cout << "fn1(60): " << (*pFun)(90) << '\n';

  return 0;
}

Solution

  • Why the pointer to the object of std::function is still valid when the variable fn1 is out of the scope?

    Let me present a simpler example, using ints. But if you are brave, you can try to read the assembler for the std::function version.

    int main () {
        int a = 0;
        int *c = nullptr;
        {
            int b = 1;
            c = &b;
        }
        a = *c;
        return a;
    }
    

    This is generated with gcc 10.2 -O0, but the other compilers have a really similar output. I will comment it to aid the understanding.

    main:
            push    rbp
            mov     rbp, rsp
            mov     DWORD PTR [rbp-4], 0    # space for `a`
            mov     QWORD PTR [rbp-16], 0   # space for `c`
            mov     DWORD PTR [rbp-20], 1   # space for `b`
            lea     rax, [rbp-20]           # int *tmp = &b  Not really, but enough for us
            mov     QWORD PTR [rbp-16], rax # c = tmp
            mov     rax, QWORD PTR [rbp-16] 
            mov     eax, DWORD PTR [rax]    # int tmp2 = *tmp
            mov     DWORD PTR [rbp-4], eax  # a = tmp2
            mov     eax, DWORD PTR [rbp-4]  
            pop     rbp                         
            ret                             # return a
    

    And the program return 1, as expected when you see the assembler. You can see, b was not "invalidated", because we did not roll the stack back, and we didnt change its value. This will be a case like the one you are in, were it works.

    Now lets enable optimizations. Here is it compiled with -O1, the lowest level.

    Here is it compiled with gcc:

    main:
            mov     eax, 0
            ret
    

    And here is it compiled with clang:

    main:                                   # @main
            mov     eax, 1
            ret
    

    And here is with visual studio:

    main    PROC                                            ; COMDAT
            mov     eax, 1
            ret     0
    main    ENDP
    

    You can see the results are diferent. It is undefined behaviour, each implementation will do as it sees fit.

    Now this is happening with some local variables in the same function. But consider this cases:

    1. It was a pointer, lets say allocated with new. You already called delete. What guaranties that that memory is not used by someone else now?

    2. When the stack grows, the value will eventually be overiden. Consider this code:

    int* foo() {
        int a = 0;
        return &a;
    }
    
    void bar() {
        int b = 1;
    }
    
    
    int main () {
        int *ptr = foo();
        bar();
        int a = *ptr;
        return a;
    }
    

    It didnt return 1 or 0. It returned 139.

    And here is a good read on this.