Re[2]: Порядок вычислений в присваивании
От: Rycharg  
Дата: 23.11.08 20:28
Оценка: -6 :)
Здравствуйте, Erop, Вы писали:

E>Не знаток, но предполагаю, что порядок не определён и всё это UB...


Я тоже не знаток,но предполагаю, что порядок вычислений определён -- справа налево.
Возьмём такой пример:

   vector<int> a(2,2);
   int i=0;
   a[i]=++i;
   cout<<a[0]<<endl;
   cout<<a[1]<<endl;

На экране стабильно появляются 2 и 1.
Re: Порядок вычислений в присваивании
От: Кодт Россия  
Дата: 24.11.08 13:48
Оценка: 14 (4)
Здравствуйте, <Аноним>, Вы писали:

А>Знатоки стандарта, подскажите!


Подсказываю: неопределённое поведение. И вот почему:
1) порядок вычисления операндов не определён;
2) впрочем, поскольку и слева, и справа — не встроенные операторы, а функции, то существуют точки следования у них и на входе и выходе;
3) таким образом, получаем неспецифицированное поведение: либо v[index], f(v), либо наоборот, f(v), v[index].
4) однако, внутри f(v) выполняется v.push_back(), что инвалидирует все ранее полученные ссылки и итераторы на элементы v
5) поэтому ветка v[index], f(v) ведёт к работе с инвалидированной ссылкой,
6) что и требовалось доказать.

А на будущее — в топку такой код, который стреляет с двух рук.
Даже если ты здесь разрулишь ситуацию с порядком вызова атомарной, по большому счёту, операции
size_t const index = v.size();
int const value = f(v);
v[index] = value;

то это разруливание будет напрочь неочевидным и однажды где-нибудь не соблюдётся
— по невнимательности
— по забывчивости (слабо через год вспомнить, почему пришлось разносить вызов функции и запись?)
— в результате совмещения версий (если между 1 и 2 или 2 и 3 строкой будет что-то постороннее вставлено)

Лучше всего — написать эту самую атомарную функцию и не париться
void expand_and_rewrite(vector<int>& v, size_t index) // 0 <= index <= v.size(), т.е. допускается запись за исходный конец вектора
{
    v.push_back(333);
    v[index] = 444;
}
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Перекуём баги на фичи!
Re[2]: Порядок вычислений в присваивании
От: Alexander G Украина  
Дата: 23.11.08 21:04
Оценка: +1
Здравствуйте, Erop, Вы писали:

E>Здравствуйте, Аноним, Вы писали:


А>>Что вычисляется сначала правая или левая часть в присваивании?


E>Не знаток, но предполагаю, что порядок не определён и всё это UB...


UB здесь таки Unspecified Behavior, а не Undefined Behavior.
Русский военный корабль идёт ко дну!
Порядок вычислений в присваивании
От: Аноним  
Дата: 23.11.08 18:22
Оценка:
Знатоки стандарта, подскажите!
Что вычисляется сначала правая или левая часть в присваивании?

Вот примерчик:
int f(std::vector<int>& arg)
{
  arg.push_back(333); 
  return 444;
}

void g()
{
  std::vector<int> v;

  size_t index = v.size();
  v.push_back(0);

  v[index] = f(v); // что сначала: vector<>::operator[] или f(v)?
}
Re: Порядок вычислений в присваивании
От: Erop Россия  
Дата: 23.11.08 20:06
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Что вычисляется сначала правая или левая часть в присваивании?


Не знаток, но предполагаю, что порядок не определён и всё это UB...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[3]: Порядок вычислений в присваивании
От: Erop Россия  
Дата: 23.11.08 22:20
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>UB здесь таки Unspecified Behavior, а не Undefined Behavior.

На практике разница не велика...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[3]: Порядок вычислений в присваивании
От: Erop Россия  
Дата: 23.11.08 22:21
Оценка:
Здравствуйте, Rycharg, Вы писали:

R>
R>   vector<int> a(2,2);
R>   int i=0;
R>   a[i]=++i;
R>   cout<<a[0]<<endl;
R>   cout<<a[1]<<endl;

R>

R>На экране стабильно появляются 2 и 1.

А что значит "стабильно"?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re: Порядок вычислений в присваивании
От: Pzz Россия https://github.com/alexpevzner
Дата: 23.11.08 23:27
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Знатоки стандарта, подскажите!

А>Что вычисляется сначала правая или левая часть в присваивании?

Надо читать стандарт. На вскидку я помню, что порядок определен для операторов &&, || и запятая. Про присваивание не помню.

Но есть тонкий момент: если операторы overloaded, то порядок перестает быть определенным! С этим надо быть осторожным.
Re[3]: Порядок вычислений в присваивании
От: K13 http://akvis.com
Дата: 24.11.08 07:11
Оценка:
R>Возьмём такой пример:

R>
R>   vector<int> a(2,2);
R>   int i=0;
R>   a[i]=++i;
R>   cout<<a[0]<<endl;
R>   cout<<a[1]<<endl;

R>

R>На экране стабильно появляются 2 и 1.

для всех компиляторов на всех платформах?
при любых режимах оптимизации?

если речь о нескольких запусках собранного бинарника, то они (разумеется) повторяются.
Re[3]: Порядок вычислений в присваивании
От: k-pax  
Дата: 24.11.08 14:01
Оценка:
R>
R>   vector<int> a(2,2);
R>   int i=0;
R>   a[i]=++i;
R>   cout<<a[0]<<endl;
R>   cout<<a[1]<<endl;

R>

R>На экране стабильно появляются 2 и 1.

только что на линухе этот примерчик собрал (gcc version 3.4.6) . стабильно 1 и 2 на экране появляться . так что, не все так однозначно
Re[2]: Порядок вычислений в присваивании
От: Аноним  
Дата: 24.11.08 18:24
Оценка:
Спасибо всем отозвавшимся, подтвердили мои подозрения.

Здравствуйте, Кодт, Вы писали:

К>А на будущее — в топку такой код, который стреляет с двух рук.

К>Даже если ты здесь разрулишь ситуацию с порядком вызова атомарной, по большому счёту, операции
К>
К>size_t const index = v.size();
К>int const value = f(v);
К>v[index] = value;
К>

К>то это разруливание будет напрочь неочевидным и однажды где-нибудь не соблюдётся
К>- по невнимательности
К>- по забывчивости (слабо через год вспомнить, почему пришлось разносить вызов функции и запись?)
К>- в результате совмещения версий (если между 1 и 2 или 2 и 3 строкой будет что-то постороннее вставлено)

К>Лучше всего — написать эту самую атомарную функцию и не париться

К>
К>void expand_and_rewrite(vector<int>& v, size_t index) // 0 <= index <= v.size(), т.е. допускается запись за исходный конец вектора
К>{
К>    v.push_back(333);
К>    v[index] = 444;
К>}
К>


Я код утрированный привел

В реальном коде есть таблица для регистрации неких ресуров.
Функция регистрации возвращает назначенный индекс ресурса,
а функция f() — это фабрика этих самых ресурсов.

Тонкость в том, что ресурсы могут быть зависимы друг от друга.
В конструкторе ресурса А вызывается регистрация ресурса Б,
которая в свою очередь получает индекс в таблице.

 resource_id_t new_id = m_resource_table.size();
 m_resource_table.push_back(0);

 shared_ptr<RuntimeResource> pres(factoryRuntimeResource(resource_tag));
 m_resource_table[new_id] = pres; // ok

 // ill-formed
 // m_resource_table[new_id] = factoryRuntimeResource(resource_tag);


В Debug vector память по другому аллочит, чем в Release, не падало и
проглядел эти грабли
Re[3]: Порядок вычислений в присваивании
От: Кодт Россия  
Дата: 24.11.08 20:04
Оценка:
Здравствуйте, <Аноним>, Вы писали:

А>Я код утрированный привел


А>В реальном коде есть таблица для регистрации неких ресуров.

А>Функция регистрации возвращает назначенный индекс ресурса,
А>а функция f() — это фабрика этих самых ресурсов.

А>Тонкость в том, что ресурсы могут быть зависимы друг от друга.

А>В конструкторе ресурса А вызывается регистрация ресурса Б,
А>которая в свою очередь получает индекс в таблице.

Здесь потенциально есть кольцевая зависимость.
Так как на момент вызова factoryRuntimeResource(resource_tag) ты уже зарезервировал слот под номером new_id, то этот самый ресурс может захотеть им воспользоваться.

Если тебе это нужно, то всё правильно: заполнение слота не является атомарной операцией.
Если же кольцевые зависимости — свидетельство ошибки (в дизайне, в кодировании, в конфиг-файле, в руках пользователя), то было бы правильным сделать просто
m_resource_table.push_back(factoryRuntimeResource(resource_tag));


Есть ситуации, когда зависимости сложные, но не кольцевые.
Например, сериализация графа объектов.
Каждый объект записывается ровно один раз. Сперва — пишется его тип; сам объект помещается в таблицу; далее сериализуется содержимое. Последующие разы — записывается только порядковый номер.
А при конструировании, соответственно, наоборот: первый раз прочитывается и немедленно (до десериализации содержимого) конструируется объект и заносится в таблицу.

В любом случае, не возникает твоей ситуации
— зарезервировал
— реконструировал
— записал
Отнюдь:
— зарезервировал и записал
— реконструировал созданную болванку
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Перекуём баги на фичи!
Re: Порядок вычислений в присваивании
От: MasterZiv СССР  
Дата: 25.11.08 07:15
Оценка:
Аноним 30 пишет:

> v[index] = f(v); // что сначала: vector<>::operator[] или f(v)?

> }
А какая разница-то?
Posted via RSDN NNTP Server 2.1 beta
Re[4]: Порядок вычислений в присваивании
От: Аноним  
Дата: 25.11.08 10:30
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здесь потенциально есть кольцевая зависимость.

К>Так как на момент вызова factoryRuntimeResource(resource_tag) ты уже зарезервировал слот под номером new_id, то этот самый ресурс может захотеть им воспользоваться.

Нет, не воспользоваться, а зарегистрировать, т.е. лишь сконструировать и получить индекс.
Операция для "воспользоваться" получает доступ к ресурсу по индексу и проверяет элемент на 0 и, если 0, то кидает исключение.
Индекс надо знать до вызова фабрики, т.к. еще есть map соответствия resource_tag -> index (в примере я его выкинул).

Кольцевая зависимость — здесь вполне нормальная ситуация.
Допустим А зависит от Б, а Б от А:
1. какой-то код регистрирует А
2. для А резервируется индекс N
3. конструктор А регистрирует Б
4. для Б резервируется индекс N+1
5. конструктор Б регистрирует А
6. для А уже есть индекс N, он и возвращается
7. В итоге А хранит индекс Б (N+1), Б хранит индекс А (N)

Для более сложных циклов в графе зависимости тоже проблем нет.
Re[2]: Порядок вычислений в присваивании
От: Аноним  
Дата: 25.11.08 10:35
Оценка:
Здравствуйте, MasterZiv, Вы писали:

>> v[index] = f(v); // что сначала: vector<>::operator[] или f(v)?

>> }
MZ>А какая разница-то?

Разница большая, т.к. f(v) меняет вектор и ранее вычисленная ссылка
в результате vector<>::operator[] становится невалидной.
Данный код был бы корректным только в случае гарантии выполнения f(v) первой.
Re[3]: Порядок вычислений в присваивании
От: Аноним  
Дата: 25.11.08 17:18
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Я код утрированный привел


А>В реальном коде есть таблица для регистрации неких ресуров.

А>Функция регистрации возвращает назначенный индекс ресурса,
А>а функция f() — это фабрика этих самых ресурсов.

А>Тонкость в том, что ресурсы могут быть зависимы друг от друга.

А>В конструкторе ресурса А вызывается регистрация ресурса Б,
А>которая в свою очередь получает индекс в таблице.

Разнеси во времени инстанциирование и инициализацию — и потомки скажут спасибо. Ибо текущий дизайн — грязный, грязный, грязный!
Re[4]: Порядок вычислений в присваивании
От: Аноним  
Дата: 26.11.08 07:38
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Разнеси во времени инстанциирование и инициализацию — и потомки скажут спасибо. Ибо текущий дизайн — грязный, грязный, грязный!


Типа разделить конструктор на конструктор и функцию Create? A-ля MFC?
И это будет гут дизайн!?

Мне вообще-то использование RAII более привлекает. Конструктор кидает исключение, если что-то не так, и никаких проблем с "недоделаными" объектами.

Вариант 1, резервирование места до конструирования:
resource_id_t ResourceRepository::registerResource(const std::string& resource_tag)
{
  resource_id_t new_id = m_resource_table.size(); // резервируем индекс
 
  // ищем или устанавливаем соответствие строкового тега и индекса
  std::pair<resource_map_t::iterator, bool> result = 
       m_resource_map.insert(resource_map_t::value_type(resource_tag, new_id));
  if (!result.second)
      return result.first->second; // возвращаем индекс уже зарегистрированного

  m_resource_table.push_back(0); // резервируем место в таблице

  shared_ptr<RuntimeResource> pres(factoryRuntimeResource(resource_tag));
  m_resource_table[new_id] = pres; // ok

  return new_id;
}


Вариант 2, резервирование места после конструирования:
resource_id_t ResourceRepository::registerResource(const std::string& resource_tag)
{
  resource_map_t::const_iterator it = m_resource_map.find(resource_tag);
  if (it != m_resource_map.end())
    return it->second; // возвращаем индекс уже зарегистрированного

  // конструируем и помещаем в таблицу
  m_resource_table.push_back(factoryRuntimeResource(resource_tag)); 

  resource_id_t new_id = m_resource_table.size() - 1; // вычисляем индекс

  m_resource_map[resource_tag] = new_id;  // запоминаем соответствие

  return new_id;
}


Второй вариант не делает никаких модификаций m_resource_table и m_resource_map до завершения конструирования.
Потенциальная проблема, что для одного resource_tag может быть создано более одного объекта (регистрация А->Б->А).

Оба варианта свободны от исходной проблемы с порядком вычислений.
Какие соображения по дизайну, коллеги?
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.