Изменение элементов списка с помощью итераторов в c ++ (подзаголовок: либо отодвигать назад, либо итераторы создают копии исходных данных?) - PullRequest
0 голосов
/ 25 сентября 2019

Хотя у меня большой опыт работы в качестве программиста на C Embedded, на самом деле я не настолько опытен в C ++.Я реализую парсер, в котором ItemsToBeFound помещается в список.

Бывает, что некоторые из этих элементов (скажем, «главные элементы») содержат больше информации, так что некоторые другие элементы («подчиненные элементы»)) для получения этой информации необходимо обратиться к «основным элементам».Эта ссылка выполняется на этапе заполнения без итераторов.

Итак:

  1. Я сохраняю указатель на главный элемент и помещаю его в список
  2. Iсохранить указатель на главный элемент в определенном поле подчиненного элемента
  3. Позже я перебираю список
  4. Сохраняю проанализированную информацию в главный элемент
  5. Когда этоприходит к процессу подчиненного элемента, я пытаюсь получить доступ к полю главного элемента.Но он не меняется!
  6. Указатель на главный элемент во время итерации отличается от исходного, поэтому указатель, содержащийся в подчиненном элементе, указывает на исходные (неизменные) данные

Любойпредложение о некоторых специальных квалификаторах, которые я мог бы использовать, чтобы провести итерацию по исходным данным?Есть ли очевидная ошибка в способе выдачи списков и / или итераторов?

Обратите внимание, что я уверен в том, что утверждено на шаге № 6 выше, потому что я наблюдал за значениями указателей с помощью отладчика.

Вот упрощенный код, показывающий описанную проблему:


class ItemToBeFound
{
private:
public:
  ...
  int specialDataToBeRetreived;
  ItemToBeFound *referenceItem;
  ...
}

std::list<ItemToBeFound> myList;

void PopulateList( void )
{
  ItemToBeFound *masterItem = new ItemToBeFound( NULL /* no refItem */ );
  myList.push_back( *masterItem );

  ItemToBeFound *slaveItem = new ItemToBeFound( masterItem );
  myList.push_back( *slaveItem );
}

void mainFunction( void )
{
  // ...
  PopulateList();
  // ...

  /* Let's iterate and do something */
  std::list<ItemToBeFound>::iterator it = myList.begin();
  while( it != itemsList.end() )
  {
    if( is_a_master_item )
    {
      it->specialDataToBeRetreived = getFromFileToBeParsed();
    }
    else /* it is a slave item */
    {
      it->specialDataToBeRetreived = it->referenceItem->specialDataToBeRetreived;

      /* But it doesn't work! */
    }

    it++;
  }

  // ...
}

В «ведомом» остальном во время итерации я ожидаю, что «specialDataToBeRetreived» я установил в ссылочном объекте, но продолжаю получать 0.

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

1 Ответ

4 голосов
/ 25 сентября 2019

Итераторы ничего не копируют.В терминах C они ведут себя почти как указатели в массиве.

Однако вы копируете элементы здесь:

void PopulateList( void )
{
  ItemToBeFound *masterItem = new ItemToBeFound( NULL /* no refItem */ );
  myList.push_back( *masterItem ); //copy of masterItem pointee

  ItemToBeFound *slaveItem = new ItemToBeFound( masterItem ); //initialized with masterItem, which does not point to the object stored in the list!
  myList.push_back( *slaveItem ); //copy of slaveItem pointee, also stores masterItem, but not object from the list

//slaveItem is leaked here, masterItem is only kept via slaveItem copy in the list
}

Если вы инициализируете такие объекты, ваши подчиненные объекты никогда не сохраняют указатели на объектыв списке, а скорее к объектам, созданным с помощью new.


Давайте пошагово пройдемся по коду (здесь я буду резиновым отладчиком):

ItemToBeFound *masterItem = new ItemToBeFound( NULL /* no refItem */ );

Вы размещаете объект в куче (у них нет имен, но давайте назовем его m1) и указатель на стек, называемый masterItem, поэтому разыменованный masterItem в точности равен m1.

  myList.push_back( *masterItem );

std::list::push_back() создает копию данного аргумента (я игнорирую семантику перемещения - они здесь не используются).Обратите внимание на аргумент - разыменованный masterItem или m1, как мы его ранее называли.
Копия объекта m1, созданного push_back, будет называться m2

Так что теперьу нас есть следующее (с a->b означает a указывает на b):

masterItem->m1
myList->m2

Следующая строка:

  ItemToBeFound *slaveItem = new ItemToBeFound( masterItem );

Здесь выинициализировать slaveItem новым объектом в куче - s1.

masterItem->m1
myList->m2
slaveItem->s1->masterItem->m1 (s1 points to masterItem, which points to m1)

Последний:

myList.push_back( *slaveItem );

И снова push_back() создает копию своего аргумента в куче(эта копия будет называться s2)

masterItem->m1
slaveItem->s1->masterItem->m1 
myList->m2
myList->s2->masterItem->m1

В конце концов у вас есть:

  • 4 объекта в куче: m1, m2, s1и s2 (все типа ItemToBeFound)
  • 2 объекта в стеке: masterItem и slaveItem (оба типа указателя)
  • один объект в глобальной области:myList (здесь можно рассматривать как объект стека)
  • m2 и s2 хранятся в myList и могут быть повторены в
  • Оба s1 и s2имеют указатели на m1
  • Нет подчиненного объекта указывает на m2

Позже, когда вы запускаете mainFunction(), объект m2 заполняется данными, но объект s2 пытается получить данные из m1, которые не были заполнены какими-либо значимымиdata.


Трудно предложить какое-либо решение без дополнительной информации.Если бы я не смог реорганизовать структуру классов, я бы использовал вместо нее std::list<std::shared_ptr<ItemToBeFound>>, что обеспечит безопасность ваших указателей и сохранит только std::weak_ptr<ItemToBeFound> referenceItem в подчиненных объектах (чтобы избежать циклических зависимостей и четко объявить владельца).

Вы могли бы иметь std::list<ItemToBeFound*> и управлять памятью вручную, но это противоречит духу современного C ++ и должно использоваться с большой осторожностью - поток может занять больше веток в C ++, чем в C, и все их сложнее отслеживатьна delete все.

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

...