Каковы гарантии порядка оценки, введенные C++17?


каковы последствия голосования в C++17 гарантии порядка оценки (P0145) на типичном коде C++?

что это меняет в таких вещах, как

i=1;
f(i++, i)

и

std::cout << f() << f() << f() ;

или

f(g(),h(),j());
2 54

2 ответа:

некоторые типичные случаи, когда порядок оценки до сих пор нет данных, указаны и действительны с C++17. Некоторое неопределенное поведение теперь не определено.

а как насчет таких вещей, как

i=1;
f(i++, i)

был неопределенным,но теперь не определен.

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, "");

пример исходит от язык программирования C++, 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 не было никакой гарантии, что первые вызовы придут в первую очередь поток.

ссылки: с принял предложение:

Постфиксные выражения вычисляются слева направо. Это включает в себя вызовы функций и выражения выбора элемента.

выражения присваивания вычисляются справа налево. Этот включает в себя составные задания.

операнды для операторов сдвига вычисляются слева направо. В сводка, следующие выражения вычисляются в для заказа, затем b, затем c, затем d:

  1. а.б
  2. a - >b
  3. a - >*b
  4. a (b1, b2, b3)
  5. b @= a
  6. a[b]
  7. a
  8. a > > b

кроме того, мы предлагаем следующее дополнительное правило: порядок вычисление выражения с участием перегруженного оператора является определяется порядок, связанный с соответствующим встроенный оператор, а не правила для вызовов функций.

редактировать Примечание: мой оригинальный ответ неверно истолкован 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() бросил бы перед другим необработанный указатель был привязан к его параметру смарт-указателя. edit: как указано T. C. пример является ошибочным, поскольку конструкция unique_ptr из необработанного указателя явна, предотвращая это скомпилировать.

также обратите внимание на этот классический вопрос (с тегом C, а не C++):

int x=0;
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));

есть четыре операции, которые происходят здесь во время вызова функции

  1. new A
  2. unique_ptr<A> конструктор
  3. new B
  4. unique_ptr<B> конструктор

порядок их был совершенно не определен, и поэтому совершенно правильный порядок (1), (3), (2), (4). Если этот заказ был выбрано и (3) бросает, то память из (1) утечек - мы еще не запустили (2), что бы предотвратить утечку.


в C++17, новые правила запрещают чередование. Из [вступление.исполнение]:

для каждого вызова функции F, для каждого вычисления A, которое происходит в пределах F, и каждого вычисления B, которое не происходит в пределах F, но оценивается в том же потоке и как часть того же обработчика сигнала (если таковой имеется), либо a секвенируется перед B, либо B является секвенирован до А.

к этому предложению есть сноска, которая гласит:

другими словами, выполнение функций не чередуются друг с другом.

это оставляет нас с двумя допустимыми порядками: (1), (2), (3), (4) или (3), (4), (1), (2). Неизвестно, какой заказ принимается, но оба они безопасны. Все заказы, где (1) (3) оба происходят до (2) и (4) теперь запрещены.