Каков «правильный» способ избежать алиасинга (например, при добавлении элемента контейнера к себе) в C ++? - PullRequest
11 голосов
/ 02 июня 2011
std::vector<int> a;
a.push_back(1);
a.push_back(a[0]);

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

(Если не понятно почему, вы не одиноки ... для меня это тоже не очевидно.)

Мои вопросы:

  1. Что такое «стандартный» способ борьбы с ним? Создание новой переменной и ее последующее присвоение чему-либо потом кажется мне немного странным. Есть ли лучший способ справиться с этим?

  2. Как вы тренируетесь , чтобы остерегаться подобных проблем? Какой шаблон (ы) вы ищете? Я понятия не имею, чтобы признать эту ситуацию; Я узнал о псевдонимах только тогда, когда узнал о ключевом слове restrict в C, и только теперь я понимаю, в чем проблема на самом деле.


Edit:

Я бы с удовольствием принял ответ, но не похоже, что на часть (2) вопроса был дан ответ. Мне интересно, какие стратегии люди используют для поиска псевдонимов в написанном ими коде.

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

Есть ли еще какие-нибудь простые вещи, на которые стоит обратить внимание?

Ответы [ 5 ]

5 голосов
/ 02 июня 2011

РЕДАКТИРОВАТЬ : Технически стандарт не требует, чтобы это было правильно, если содержащийся тип имеет конструктор копирования без бросков. Я не знаю ни одной реализации, где это все равно не выполняется, так как это потребовало бы создания двух реализаций push_back, когда универсальная одинаково эффективна во всех случаях.

Псевдоним - это проблема в целом, но не в этом конкретном случае. Код:

assert( v.size() > 0 );
v.push_back( v[0] );

Гарантируется, что стандарт корректен (C ++ 03) через гарантии исключений (которые являются действительно веской причиной не реализовывать свои собственные контейнеры, вы, вероятно, не получите их правильно). В частности, §23.1 [lib.container.requirements] / 10 диктатов:

Если не указано иное (см. 23.2.1.3 и 23.2.4.3) [ ПРИМЕЧАНИЕ: обе эти ссылки относятся к insert на deque и vector соответственно ] всех типов контейнеров, определенных в этом пункте соответствовать следующим дополнительным требованиям:

- если исключение выдается функцией push_back () или push_front (), эта функция не имеет никаких эффектов .

Если важным битом является то, что если в операции выдается любое исключение, контейнер остается нетронутым , и это означает, что ни один итератор не будет признан недействительным, что, в свою очередь, означает, что исходная область памяти остается нетронутой до тех пор, пока не будет гарантировано, что не будет выброшено никаких исключений (за исключением исключение каламбура деструкторов). Поскольку в общем случае конструкторы копирования могут throw, реализация должна обеспечить выполнение всех копий перед уничтожением любого объекта.

Это становится более очевидным в C ++ 0x, когда объекты не копируются из одного места в другое, а перемещаются . Поскольку копия нового элемента может быть сгенерирована, она должна быть выполнена до того, как будет выполнен любой из ходов, иначе вы останетесь в ситуации, когда некоторые объекты в исходном контейнере были признаны недействительными .

4 голосов
/ 02 июня 2011

Я думаю, это будет безопасно:

std::vector<int> a(1);
a.push_back(1);
a.push_back(int(a[0]));
0 голосов
/ 03 июня 2011

Это, вероятно, бесполезный ответ для вас, но IMHO "правильный" способ заключается в том, что класс контейнера должен правильно обрабатывать псевдонимы, чтобы вызывающему не приходилось беспокоиться об этом. В частности, push_back () (или эквивалентный) должен делать следующее:

// C++-ish pseudo-code, exception-safety left as an exercise for the reader
void push_back(const T & t)
{
   if (current_size == alloced_size)
   {
      // Oops, our data array is full.  Time to trade it in for a bigger one
      T * newArray = new T[alloced_size*2];
      copy_items(newArray, current_array, current_size);
      newArray[current_size++] = t;
      delete [] current_array;    // delete old array only AFTER all references to t
      current_array = new_array;
      alloced_size *= 2;
   }
   else current_array[current_size++] = t;
}
0 голосов
/ 02 июня 2011

В push_back(const T& el); реализации для проверки, находится ли el внутри массива или другого внутреннего хранилища.Это единственный политкорректный способ решения таких проблем.

Контейнер должен обрабатывать это как разные контейнеры - разные правила безопасности.

0 голосов
/ 02 июня 2011

Я просто обожаю это, поэтому, пожалуйста, не считайте это Евангелием, но сработает ли это?

a.push_back(1);
a.push_back(&(new int(a[0])));
...