Вот еще один пример на неоднозначность синтаксических конструкция в C++.
Рассмотрим следующую программу.
#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';
}
Спрашивается, какие значения
use_count() программа выведет на консоль.
Сложность ответа на вопрос может вызывать наличие конструкции
std::shared_ptr<int>( p );
во внутреннем блоке кода функции
main. То есть как эта конструкция влияет на вывод?
Согласно стандарту C++ (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.... |
|
То есть согласно этой цитате данное предложение
std::shared_ptr<int>( p );
представляет собой предложение выражения, содержащее выражение явного (функционального) приведения типов.
Это предложение никак не может повлиять на вывод значения
p.use_count() в последующих предложениях.
Но, с другой стороны, данное предложение можно рассматривать как объявление локальной переменной
p, так как деклараторы можно заключать в круглые скобки.
Действительно, например, следующее предложение
int ( x );
является корректным объявлением переменной
x, имеющей тип
int. Таким образом предложение
std::shared_ptr<int>( p );
является объявлением локальной переменной
p, имеющей тип
std::shared_ptr<int>.
Для наглядности можно спецификатор типа и декларатор разделить дополнительным пробелом
std::shared_ptr<int> ( p );
Как в C++ разрешается такая неоднозначноть? Согласно разделу 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 |
|
Итак, стандарт разрешает эту неоднозначность в пользу объявления переменной.
Следовательно в программе во внутреннем блоке объявляется новая локальная переменная
p, которая скрывает одноименную переменную
p, объявленную во внешнем блоке, используя конструктор по умолчанию
constexpr shared_ptr() noexcept;
Согласно описанию этого конутрктора
цитата: |
2 Effects: Constructs an empty shared_ptr object. 3 Postconditions: use_count() == 0 && get() == nullptr |
|
Учитывая все сказанное, вывод программы на консоль будет выглядеть следующим образом:
#1: 1
#2: 1
#3: 0
#4: 1
То есть в третьем предложении вывода в поток будет выведено на консоль значение
use_count, равное
0 для нового только что созданного локального объекта
p.
А как это предложение с объявлением превратить в предложение выражения?
Достаточно просто заключить тело предложения в круглые скобки, как показано в программе ниже.
#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';
}
И тогда вывод на консоль уже будет выглядеть следующим образом:
#1: 1
#2: 1
#3: 1
#4: 1
Альтернативный подход, если не использовать круглые скобки, состоит в том, чтобы записать выражение явного функционального приведения типа с использованием фигурных скобок. Ниже показано, как будет выглядеть соответствующее предложение.
#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';
}
Вывод на консоль будет таким же, как показано выше
#1: 1
#2: 1
#3: 1
#4: 1
Это сообщение я создал на основе вопроса на
Stackoverflow Understanding C++ std::shared_ptr when used with temporary objects и моего ответа на него.