Порядок выполнения С++ в цепочке методов

Вывод этой программы:

#include <iostream> 
class c1
{   
  public:
    c1& meth1(int* ar) {
      std::cout << "method 1" << std::endl;
      *ar = 1;
      return *this;
    }
    void meth2(int ar)
    {
      std::cout << "method 2:"<< ar << std::endl;
    }
};

int main()
{
  c1 c;
  int nu = 0;
  c.meth1(&nu).meth2(nu);
}

Есть:

method 1
method 2:0

Почему nu не 1 при запуске meth2()?

97
задан 16 мая '16 в 14:00
источник поделиться
4 ответов

Поскольку порядок оценки не указан.

Вы видите nu в main, который оценивается до 0, прежде чем вызывается даже meth1. Это проблема с цепочкой. Я советую не делать этого.

Просто сделайте приятную, простую, понятную, легко читаемую и понятную программу:

int main()
{
  c1 c;
  int nu = 0;
  c.meth1(&nu);
  c.meth2(nu);
}
62
ответ дан 16 мая '16 в 14:02
источник

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

1.9 Выполнение программы

...

  1. За исключением случаев, когда отмечено, оценки операндов отдельных операторов и подвыражений отдельных выражения не подвержены влиянию. Вычисления значений операндов оператор упорядочивается перед вычислением значения результата оператора. Если побочный эффект скаляра объект не влияет на какой-либо другой побочный эффект на один и тот же скалярный объект или вычисление значения используя значение одного и того же скалярного объекта, и они не являются потенциально параллельными, поведение undefined

а также:

5.2.2 Вызов функции

...

  1. [Примечание: Оценки постфиксного выражения и аргументов всенепоследованы относительно одного другой. Все побочные эффекты оценок аргументов секвенированы до ввода функции - примечание конца)

Итак, для вашей строки c.meth1(&nu).meth2(nu); рассмотрим, что происходит в операторе в терминах оператора вызова функции для окончательного вызова meth2, поэтому мы четко видим разбивку на постфиксное выражение и аргумент nu:

operator()(c.meth1(&nu).meth2, nu);

Оценки постфиксного выражения и аргумента для окончательного вызова функции (т.е. постфиксное выражение c.meth1(&nu).meth2 и nu) не имеют последовательности относительно друг друга в соответствии с правилом вызова функции выше. Поэтому побочный эффект вычисления постфиксного выражения на скалярном объекте ar не зависит от оценки аргумента nu перед вызовом функции meth2. По правилу выполнения программы выше, это поведение undefined.

Другими словами, компилятор не должен оценивать аргумент nu при вызове meth2 после вызова meth1 - он свободен предположить, что побочные эффекты meth1 не влияют на nu оценка.

Код сборки, созданный выше, содержит следующую последовательность в функции main:

  • Переменная nu выделяется в стеке и инициализируется 0.
  • Регистр (ebx в моем случае) получает копию значения nu
  • Адреса nu и c загружаются в регистры параметров
  • meth1 называется
  • Регистр возвращаемого значения и ранее кэшированное значение nu в регистре ebx загружаются в регистры параметров
  • meth2 называется

Критически, на шаге 5 выше компилятор позволяет кэшировать значение nu с шага 2 для повторного использования в вызове функции meth2. Здесь он игнорирует возможность того, что nu может быть изменен вызовом meth1 - 'undefined поведения "в действии.

ПРИМЕЧАНИЕ: Этот ответ по существу изменился из его первоначальной формы. Мое первоначальное объяснение с точки зрения побочных эффектов вычисления операндов, которые не были секвенированы до окончательного вызова функции, были неправильными, потому что они есть. Проблема заключается в том, что вычисление самих операндов неопределенно секвенировано.

27
ответ дан 16 мая '16 в 14:17
источник

В стандарте С++ 1998 года, раздел 5, абзац 4

За исключением тех случаев, когда отмечено, порядок оценки операндов отдельных операторов и подвыражений отдельных выражений и порядок, в котором происходят побочные эффекты, не определены. Между предыдущими и следующая точка последовательности скалярный объект должен иметь значение, которое его хранимое значение изменялось не более одного раза путем оценки выражения. Кроме того, к предыдущему значению следует обращаться только для определения значения, которое необходимо сохранить. Требования настоящего параграфа удовлетворяются для каждого допустимого упорядочения подвыражений полного выражения; в противном случае поведение undefined.

(Я пропустил ссылку на сноску № 53, которая не имеет отношения к этому вопросу).

По существу, &nu должен быть оценен перед вызовом c1::meth1(), а nu должен быть оценен перед вызовом c1::meth2(). Однако не требуется, чтобы nu оценивался до &nu (например, разрешено сначала оценивать nu, затем &nu, а затем вызывается c1::meth1() - что может быть вашим компилятором делать). Следовательно, выражение *ar = 1 в c1::meth1() не гарантируется, чтобы быть оцененным до того, как nu в main() будет оценено, чтобы передать его в c1::meth2().

Более поздние стандарты С++ (которых я не имею на ПК, которые я использую сегодня) имеют по существу одно и то же предложение.

9
ответ дан 16 мая '16 в 14:41
источник

Я думаю, что при компиляции, прежде чем функции meth1 и meth2 действительно вызываются, параметрам передают их. Я имею в виду, когда вы используете "c.meth1 (& nu).meth2 (nu);" значение nu = 0 было передано в meth2, поэтому не имеет значения, что "nu" изменено последним.

вы можете попробовать следующее:

#include <iostream> 
class c1
{
public:
    c1& meth1(int* ar) {
        std::cout << "method 1" << std::endl;
        *ar = 1;
        return *this;
    }
    void meth2(int* ar)
    {
        std::cout << "method 2:" << *ar << std::endl;
    }
};

int main()
{
    c1 c;
    int nu = 0;
    c.meth1(&nu).meth2(&nu);
    getchar();
}

он получит ответ, который вы хотите

5
ответ дан 16 мая '16 в 14:20
источник

Другие вопросы по меткам или Задайте вопрос