Каковы последствия утвержденных в C ++ 17 гарантий порядка оценки (P0145) для типичного кода C ++?
Что изменится в следующих вещах?
i = 1;
f(i++, i)
а также
std::cout << f() << f() << f();
или
f(g(), h(), j());
Каковы последствия утвержденных в C ++ 17 гарантий порядка оценки (P0145) для типичного кода C ++?
Что изменится в следующих вещах?
i = 1;
f(i++, i)
а также
std::cout << f() << f() << f();
или
f(g(), h(), j());
Ответы:
Некоторые общие случаи, когда порядок оценки до сих пор не определен , указаны и действительны с C++17
. Некоторое неопределенное поведение теперь не указано.
i = 1;
f(i++, i)
было неопределенным, но теперь не определено. В частности, не указывается порядок, в котором каждый аргумент f
оценивается относительно других. i++
могут быть оценены раньше i
или наоборот. В самом деле, он может оценить второй вызов в другом порядке, несмотря на то, что он находится под тем же компилятором.
Однако оценка каждого аргумента должна выполняться полностью, со всеми побочными эффектами, перед выполнением любого другого аргумента. Таким образом, вы можете получить f(1, 1)
(сначала оценивается второй аргумент) или f(1, 2)
(сначала оценивается первый аргумент). Но вы никогда не получите f(2, 2)
ничего подобного.
std::cout << f() << f() << f();
был не указан, но он станет совместимым с приоритетом операторов, так что первая оценка f
будет первой в потоке (примеры ниже).
f(g(), h(), j());
по-прежнему имеет неопределенный порядок оценки g, h и j. Обратите внимание, что getf()(g(),h(),j())
в правилах указано, что getf()
будет выполняться раньше g, h, j
.
Также обратите внимание на следующий пример из текста предложения:
std::string s = "but I have heard it works even if you don't believe in it"
s.replace(0, 4, "").replace(s.find("even"), 4, "only")
.replace(s.find(" don't"), 6, "");
Пример взят из The C ++ Programming Language , 4-е издание, Stroustrup, и раньше был неопределенным поведением, но с C ++ 17 он будет работать, как ожидалось. Аналогичные проблемы были и с возобновляемыми функциями ( .then( . . . )
).
В качестве другого примера рассмотрим следующее:
#include <iostream>
#include <string>
#include <vector>
#include <cassert>
struct Speaker{
int i =0;
Speaker(std::vector<std::string> words) :words(words) {}
std::vector<std::string> words;
std::string operator()(){
assert(words.size()>0);
if(i==words.size()) i=0;
// Pre-C++17 version:
auto word = words[i] + (i+1==words.size()?"\n":",");
++i;
return word;
// Still not possible with C++17:
// return words[i++] + (i==words.size()?"\n":",");
}
};
int main() {
auto spk = Speaker{{"All", "Work", "and", "no", "play"}};
std::cout << spk() << spk() << spk() << spk() << spk() ;
}
С C ++ 14 и до того, как мы сможем (и будем) получать такие результаты, как
play
no,and,Work,All,
вместо того
All,work,and,no,play
Обратите внимание, что приведенное выше действует так же, как
(((((std::cout << spk()) << spk()) << spk()) << spk()) << spk()) ;
Но все же до C ++ 17 не было гарантии, что первые вызовы попадут в поток первыми.
Ссылки: Из принятого предложения :
Выражения Postfix оцениваются слева направо. Сюда входят вызовы функций и выражения выбора членов.
Выражения присваивания оцениваются справа налево. Сюда входят составные задания.
Операнды для операторов сдвига оцениваются слева направо. Таким образом, следующие выражения вычисляются в порядке a, затем b, затем c, затем d:
- ab
- а-> б
- а -> * б
- а (b1, b2, b3)
- б @ = а
- а [б]
- а << б
- а >> б
Кроме того, мы предлагаем следующее дополнительное правило: порядок вычисления выражения, включающего перегруженный оператор, определяется порядком, связанным с соответствующим встроенным оператором, а не правилами для вызовов функций.
Изменить примечание: мой первоначальный ответ неверно истолкован a(b1, b2, b3)
. Порядок b1
, b2
, b3
до сих пор не определен. (спасибо @KABoissonneault, всем комментаторам.)
Однако, (как @Yakk указывает) , и это очень важно: Даже когда b1
, b2
, b3
нетривиальные выражения, каждый из них полностью оценены и связаны с соответствующим параметром функции До начала быть оценены другими. Стандарт гласит это так:
§5.2.2 - Вызов функции 5.2.2.4:
. . . Постфиксное-выражение упорядочивается перед каждым выражением в списке-выражении и любым аргументом по умолчанию. Каждое вычисление значения и побочный эффект, связанный с инициализацией параметра, а также сама инициализация, упорядочиваются перед каждым вычислением значения и побочным эффектом, связанным с инициализацией любого последующего параметра.
Однако одно из этих новых предложений отсутствует в черновике GitHub :
Каждое вычисление значения и побочный эффект, связанный с инициализацией параметра, а также сама инициализация, упорядочиваются перед каждым вычислением значения и побочным эффектом, связанным с инициализацией любого последующего параметра.
Примером является там. Он решает проблемы десятилетней давности ( как объяснил Херб Саттер ) с исключительной безопасностью, когда такие вещи, как
f(std::unique_ptr<A> a, std::unique_ptr<B> b);
f(get_raw_a(), get_raw_a());
будет утечка, если один из вызовов get_raw_a()
будет сгенерирован до того, как другой необработанный указатель будет привязан к его параметру интеллектуального указателя.
Как указывает TC, пример ошибочен, поскольку конструкция unique_ptr из необработанного указателя является явной, что предотвращает его компиляцию. *
Также обратите внимание на этот классический вопрос (помеченный C , а не C ++ ):
int x=0;
x++ + ++x;
все еще не определено.
a
, затем b
, затем c
, затем d
», а затем отображается a(b1, b2, b3)
, предполагая, что все b
выражения не обязательно оцениваются в любом порядке (в противном случае это было бы a(b, c, d)
)
a(b1()(), b2()())
может заказать b1()()
и b2()()
в любом порядке, но он не может делать b1()
то b2()()
тогда b1()()
: он может больше не перемежать их казни. Короче говоря, «8. АЛЬТЕРНАТИВНЫЙ ПОРЯДОК ОЦЕНКИ ФУНКЦИОНАЛЬНЫХ ЗВОНКОВ» был частью утвержденного изменения.
f(i++, i)
был неопределенным. Сейчас это не указано. Пример строки Страуструпа, вероятно, был неопределенным, а не неопределенным. `f (get_raw_a (), get_raw_a ());` не будет компилироваться, поскольку соответствующий unique_ptr
конструктор указан явно. Наконец, x++ + ++x
не определено, точка.
В C ++ 14 небезопасно было следующее:
void foo(std::unique_ptr<A>, std::unique_ptr<B>);
foo(std::unique_ptr<A>(new A), std::unique_ptr<B>(new B));
Во время вызова функции здесь происходят четыре операции
new A
unique_ptr<A>
конструкторnew B
unique_ptr<B>
конструкторИх порядок был полностью неопределенным, и поэтому вполне допустимым порядком является (1), (3), (2), (4). Если этот порядок был выбран и (3) выбрасывается, то утечка памяти из (1) - мы еще не запускали (2), что предотвратило бы утечку.
В C ++ 17 новые правила запрещают чередование. Из [intro.execution]:
Для каждого вызова функции F, для каждой оценки A, которая происходит в F, и каждой оценки B, которая не встречается в F, но оценивается в том же потоке и как часть того же обработчика сигналов (если есть), либо A упорядочивается до B или B находится перед A.
К этому предложению есть сноска, которая гласит:
Другими словами, выполнение функций не чередуется друг с другом.
Это оставляет нам два действительных порядка: (1), (2), (3), (4) или (3), (4), (1), (2). Не указано, какой порядок выбран, но оба они безопасны. Все порядки, в которых (1) (3) оба встречаются до (2) и (4), теперь запрещены.
Я нашел несколько заметок о порядке оценки выражений:
Определенный порядок оценки гарантирует окружающие перегруженные операторы и правила полных аргументов, добавленные в C ++ 17. Но остается неясным, какой аргумент идет первым. В C ++ 17 теперь указано, что выражение, указывающее, что нужно вызвать (код слева от (вызова функции), идет перед аргументами, и какой бы аргумент не оценивался первым, он полностью оценивается перед следующим. запущен, а в случае объектного метода значение объекта оценивается раньше, чем аргументы метода.
21) Каждое выражение в списке выражений, разделенных запятыми, в инициализаторе в скобках оценивается как при вызове функции (с неопределенной последовательностью )
Язык C ++ не гарантирует порядок, в котором оцениваются аргументы вызова функции.
В P0145R3.Refining Expression Evaluation Order для идиоматического C ++ я обнаружил:
Вычисление значения и связанный с ним побочный эффект постфиксного-выражения упорядочиваются перед таковыми из выражений в списке-выражений. Инициализации объявленных параметров имеют неопределенную последовательность без чередования.
Но я не нашел его в стандарте, вместо этого в стандарте я нашел:
6.8.1.8 Последовательное выполнение [intro.execution] Выражение X считается упорядоченным перед выражением Y, если каждое вычисление значения и каждый побочный эффект, связанный с выражением X, упорядочен перед каждым вычислением значения и каждым побочным эффектом, связанным с выражением Y .
6.8.1.9 Последовательное выполнение [intro.execution] Каждое вычисление значения и побочный эффект, связанный с полным выражением, упорядочивается перед каждым вычислением значения и побочным эффектом, связанным со следующим оцениваемым полным выражением.
7.6.19.1 Оператор-запятая [expr.comma] Пара выражений, разделенных запятой, оценивается слева направо; ...
Итак, я сравнил соответствующее поведение в трех компиляторах для 14 и 17 стандартов. Исследуемый код:
#include <iostream>
struct A
{
A& addInt(int i)
{
std::cout << "add int: " << i << "\n";
return *this;
}
A& addFloat(float i)
{
std::cout << "add float: " << i << "\n";
return *this;
}
};
int computeInt()
{
std::cout << "compute int\n";
return 0;
}
float computeFloat()
{
std::cout << "compute float\n";
return 1.0f;
}
void compute(float, int)
{
std::cout << "compute\n";
}
int main()
{
A a;
a.addFloat(computeFloat()).addInt(computeInt());
std::cout << "Function call:\n";
compute(computeFloat(), computeInt());
}
Результаты (более последовательным является лязг):
<style type="text/css">
.tg {
border-collapse: collapse;
border-spacing: 0;
border-color: #aaa;
}
.tg td {
font-family: Arial, sans-serif;
font-size: 14px;
padding: 10px 5px;
border-style: solid;
border-width: 1px;
overflow: hidden;
word-break: normal;
border-color: #aaa;
color: #333;
background-color: #fff;
}
.tg th {
font-family: Arial, sans-serif;
font-size: 14px;
font-weight: normal;
padding: 10px 5px;
border-style: solid;
border-width: 1px;
overflow: hidden;
word-break: normal;
border-color: #aaa;
color: #fff;
background-color: #f38630;
}
.tg .tg-0pky {
border-color: inherit;
text-align: left;
vertical-align: top
}
.tg .tg-fymr {
font-weight: bold;
border-color: inherit;
text-align: left;
vertical-align: top
}
</style>
<table class="tg">
<tr>
<th class="tg-0pky"></th>
<th class="tg-fymr">C++14</th>
<th class="tg-fymr">C++17</th>
</tr>
<tr>
<td class="tg-fymr"><br>gcc 9.0.1<br></td>
<td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td>
<td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td>
</tr>
<tr>
<td class="tg-fymr">clang 9</td>
<td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute float<br>compute int<br>compute</td>
<td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute float<br>compute int<br>compute</td>
</tr>
<tr>
<td class="tg-fymr">msvs 2017</td>
<td class="tg-0pky">compute int<br>compute float<br>add float: 1<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td>
<td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td>
</tr>
</table>