2016-11-15 9 views
12

В то время как рефакторинг некоторых кодеков сегодня, чтобы изменить исходные указатели на std::unique_ptr, я столкнулся с проблемой сегментации из-за ошибки order of evaluation.C++ 17 выражение оценки порядка и std :: move

старый код сделал что-то вроде следующего:

void add(const std::string& name, Foo* f) 
{ 
    _foo_map[name] = f; 
} 

void process(Foo* f) 
{ 
    add(f->name, f); 
} 

Первый, наивные, рефакторинга кода использовать std::unique_ptr:

void add(const std::string& name, std::unique_ptr<Foo> f) 
{ 
    _foo_map[name] = std::move(f); 
} 

void process(std::unique_ptr<Foo> f) 
{ 
    add(f->name, std::move(f)); // segmentation-fault on f->name 
} 

после рефакторинга кода вызывает ошибку сегментации, поскольку Второй аргумент (std::move(f)) обрабатывается первым, а затем 1-й аргумент (f->name) разыменовывает перемещенную переменную, стрелу!

Возможные исправления для этого должны получить ручку на Foo::nameперед тем перемещая его в вызове add:

void process(std::unique_ptr<Foo> f) 
{ 
    const std::string& name = f->name; 
    add(name, std::move(f)); 
} 

Или, может быть:

void process(std::unique_ptr<Foo> f) 
{ 
    Foo* fp = f.get(); 
    add(fp->name, std::move(f)); 
} 

Оба эти решения требуют дополнительных линий кода и не кажутся почти такими же сложными, как и исходный (хотя и UB) вызов add.

Вопросы:

  • ли либо из 2 предложенных решений выше идиоматической C++, а если нет, то есть лучшая альтернатива?

  • Я вижу, что есть перемены приходящих n C++ 17 из-за P0145R3 - Refining Expression Evaluation Order for Idiomatic C++. Изменит ли это какое-либо из вышеупомянутых решений/предотвратит их подводные камни?

+1

Я бы сказал, что «идиоматическим» решением было бы реорганизовать функцию «добавить» или добавить перегрузку, которая принимает объект 'std :: unique_ptr '. –

+0

@Someprogrammerdude, который просто переместил бы требование получить дескриптор по имени из 'process', чтобы' add' хотя правильно? Или P0145R3 решает эту проблему? –

+1

IMO - это, как правило, хорошая идея подталкивать эти детали как можно дальше по стеку. Абстракция - одна из основных частей OO и C++. –

ответ

6

Для меня эти 2 предложения выглядят плохо. В любом из них вы отдаете свой Foo объект с переходом. Это означает, что после этого вы больше не можете делать какие-либо предположения об этом состоянии. Он может быть освобожден в функции add до того, как будет обработан первый аргумент (ссылка на строку или указатель на объект). Да, он будет работать в текущей реализации, но он может сломаться, как только кто-либо коснется реализации add или чего-то еще глубже в нем.

Безопасные пути:

  • Сделать копию строки и передать в качестве первого аргумента add
  • реорганизовать add метод, который он только принимает один аргумент типа Foo и извлекает Foo::name внутри метод и не принимает его в качестве аргумента. Однако у вас все еще есть такая же проблема внутри метода add.

Во втором подходе вы должны быть в состоянии обойти эту проблему порядка оценки сначала создать новую запись карты (со значением по умолчанию) и получить изменяемую ссылку на него и присваивая значение впоследствии:

auto& entry = _foo_map[f->name]; 
entry = std::move(f); 

Не уверен, поддерживает ли ваша реализация карты получение изменяемой ссылки на записи, но для многих она должна работать.

Если я думаю об этом снова, вы также можете пойти по принципу «скопировать имя». В любом случае его нужно скопировать для ключа карты. Если вы скопируете его вручную, вы можете переместить его для ключа, нет накладных расходов.

std::string name = f->name; 
_foo_map[std::move(name)] = std::move(f); 

Edit:

Как было отмечено в комментариях, что должно быть возможно непосредственно назначить _foo_map[f->name] = std::move(f) внутри функции add, так как порядок оценки гарантируется здесь.

+0

Действительно ли необходимо * скопировать имя *? '[]' и '=' являются просто синтаксическим сахаром для функций 'operator []' и 'operator ='. Таким образом, инструкция 'map [f-> name] = std :: move (foo)' будет 'map.operator [] (f-> name) .operator = (std :: move (foo))' correct? А в P0145R3 порядок оценки этих нескольких вызовов функций определяется правильным поведением? –

+0

Не уверен на 100%, поскольку я не думал об этом преобразовании и стандарте подробно. Если это так, как вы говорите, можно избежать ручной остановки. Но так или иначе строка будет скопирована рано или поздно, так как внутри карты новая запись сохраняется как пара ключ/значение. И ключ - это копия строки, если ваш тип ключа - std :: string. – Matthias247

+4

Вы все равно можете выполнить '_foo_map [f-> name] = std :: move (f)', потому что, даже если 'f' сначала обращается в ссылку r-value, он не будет перемещен из пока есть что-то, что нужно переместить, что было бы результатом '_foo_map [f-> name]' – Altainia

1

Вы могли бы сделать add взять f по ссылке, что позволяет избежать ненужного копирования/перемещения (один и тот же экземпляр/ход, который громит f->name):

void add(const std::string& name, std::unique_ptr<Foo> && f) 
{ 
    _foo_map[name] = std::move(f); 
} 

void process(std::unique_ptr<Foo> f) 
{ 
    add(f->name, std::move(f)); 
} 

foo_map[name] должны быть оценены, прежде чем operator= называется на ней , поэтому даже если name ссылается на что-то в зависимости от f, нет проблем.

+0

, что не устраняет проблему, которая 'add()' не должна касаться имени после 'f' потребляется. Это уже строка 'add (f-> name, std :: move (f))' это зло и должно быть исправлено. – cmaster

+0

@cmaster 'f' не потребляется до вызова' operator = 'внутри' add'. 'std :: move' ничего не потребляет. И это гарантировано после чтения 'name', потому что' operator = 'не может быть вызван до тех пор, пока его операнды не будут оценены. –

+0

Проблема состоит в том, что 'add()' вызывается с двумя запутанными аргументами: модификация одного (потребление путем перемещения/назначения) приводит к изменению состояния в другом (ссылка становится недействительной). Это никогда не бывает хорошим, и его следует избегать любой ценой, если вы хотите, чтобы ваш код оставался ремонтопригодным. – cmaster

 Смежные вопросы

  • Нет связанных вопросов^_^