Search code examples
c++syntaxexpressionshared-ptrdeclaration

Understanding C++ std::shared_ptr when used with temporary objects


I am trying to understand the use of a shared_ptr p when it is used in the construction of an unnamed shared_ptr and the effects this has on p. I was toying with my own examples and wrote the following piece of code:

shared_ptr<int> p(new int(42));
cout << p.use_count() << '\n';          
{ 
  cout << p.use_count() << '\n';
  shared_ptr<int>(p);
  cout << p.use_count() << '\n';
}
cout << p.use_count() << '\n';

Output:
1
1
0
1
  1. Is it correct that line 5, uses p to create a temp. shared_ptr (i.e an unnamed shared_ptr)?
  2. If so why isn't the use_count increased. Is the temp.object destroyed even before we exit the block at line 7.
  3. If it is destroyed and p's use count becomes zero inside the block, how come it is 1 again after we exit the block?

If I would have used a named shared_ptr q on line 5, i.e:

shared_ptr<int>q(p);

Everything would work as expected, inside the block after line 5 the use count would be 2 and after we exit the block it would be 1 again.


Solution

  • According to the C++ Standard (8.5.1.3 Explicit type conversion (functional notation))

    1 A simple-type-specifier (10.1.7.2) or typename-specifier (17.7) followed by a parenthesized optional expressionlist or by a braced-init-list (the initializer) constructs a value of the specified type given the initializer...

    So the expression in this expression statement

    shared_ptr<int>(p);
    

    looks like an explicit type conversion (functional) expression.

    On the other hand, a declarator in a declaration can be enclosed in parentheses. For example

    int ( x );
    

    is a valid declaration.

    So this statement

    shared_ptr<int>(p);
    

    can be interpretated as a declaration like

    shared_ptr<int> ( p );
    

    So there is an abiguity.

    The C++ Standard resolves this ambiguity the following way (9.8 Ambiguity resolution)

    1 There is an ambiguity in the grammar involving expression-statements and declarations: An expression statement with a function-style explicit type conversion (8.5.1.3) as its leftmost subexpression can be indistinguishable from a declaration where the first declarator starts with a (. In those cases the statement is a declaration.

    Thus this statement in the inner code block

    shared_ptr<int>(p);
    

    is a declaration of a new shared pointer with the name p that hides the previous declaration of the object with the same name p in the outer code block and that is created by using the defalut constructor

    constexpr shared_ptr() noexcept;
    

    According to the description of this constructor

    2 Effects: Constructs an empty shared_ptr object.

    3 Postconditions: use_count() == 0 && get() == nullptr.

    If you want to deal with an expression instead of the declaration then all you need to do is to enclose the body of the statement in parentheses getting a primary expression in an expression statement.

    Here is a demonstrative program.

    #include <iostream>
    #include <memory>
    
    int main() 
    {
        std::shared_ptr<int> p( new int( 42 ) );
    
        std::cout << "#1: " << p.use_count() << '\n';          
    
        { 
            std::cout << "#2: " << p.use_count() << '\n';
    
            ( std::shared_ptr<int>( p ) );
    
            std::cout << "#3: " << p.use_count() << '\n';
        }
    
        std::cout << "#4: " << p.use_count() << '\n';
    
        return 0;
    }
    

    In this case its output is

    #1: 1
    #2: 1
    #3: 1
    #4: 1