Каковы последствия утвержденных в C ++ 17 гарантий порядка оценки (P0145) для типичного кода C ++?
Что изменится в следующих вещах?
i = 1;
f(i++, i)
а также
std::cout << f() << f() << f();
или
f(g(), h(), j());
c++
c++17
operator-precedence
Йохан Лундберг
источник
источник
Ответы:
Некоторые общие случаи, когда порядок оценки до сих пор не определен , указаны и действительны с
C++17
. Некоторое неопределенное поведение теперь не указано.было неопределенным, но теперь не определено. В частности, не указывается порядок, в котором каждый аргумент
f
оценивается относительно других.i++
могут быть оценены раньшеi
или наоборот. В самом деле, он может оценить второй вызов в другом порядке, несмотря на то, что он находится под тем же компилятором.Однако оценка каждого аргумента должна выполняться полностью, со всеми побочными эффектами, перед выполнением любого другого аргумента. Таким образом, вы можете получить
f(1, 1)
(сначала оценивается второй аргумент) илиf(1, 2)
(сначала оценивается первый аргумент). Но вы никогда не получитеf(2, 2)
ничего подобного.был не указан, но он станет совместимым с приоритетом операторов, так что первая оценка
f
будет первой в потоке (примеры ниже).по-прежнему имеет неопределенный порядок оценки g, h и j. Обратите внимание, что
getf()(g(),h(),j())
в правилах указано, чтоgetf()
будет выполняться раньшеg, h, j
.Также обратите внимание на следующий пример из текста предложения:
Пример взят из 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 не было гарантии, что первые вызовы попадут в поток первыми.
Ссылки: Из принятого предложения :
Изменить примечание: мой первоначальный ответ неверно истолкован
a(b1, b2, b3)
. Порядокb1
,b2
,b3
до сих пор не определен. (спасибо @KABoissonneault, всем комментаторам.)Однако, (как @Yakk указывает) , и это очень важно: Даже когда
b1
,b2
,b3
нетривиальные выражения, каждый из них полностью оценены и связаны с соответствующим параметром функции До начала быть оценены другими. Стандарт гласит это так:Однако одно из этих новых предложений отсутствует в черновике 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 ++ ):
все еще не определено.
источник
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 ++ 17
В 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]:
К этому предложению есть сноска, которая гласит:
Это оставляет нам два действительных порядка: (1), (2), (3), (4) или (3), (4), (1), (2). Не указано, какой порядок выбран, но оба они безопасны. Все порядки, в которых (1) (3) оба встречаются до (2) и (4), теперь запрещены.
источник
Я нашел несколько заметок о порядке оценки выражений:
В P0145R3.Refining Expression Evaluation Order для идиоматического C ++ я обнаружил:
Но я не нашел его в стандарте, вместо этого в стандарте я нашел:
Итак, я сравнил соответствующее поведение в трех компиляторах для 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>
источник