列表项和迭代器:是推回还是迭代器创建原始数据的副本?

List items and iterators: do either push back or iterators create copies of original data?

虽然我有很多C Embedded Programmer 的经验,但实际上我对C++ 不是很熟练。我正在实现一个解析器,其中 ItemsToBeFound 被推入列表。

碰巧其中一些项目(比如说主项目)携带更多信息,因此其他一些项目(从项目 ) 需要参考 主条目 来检索这些信息。 link 在填充阶段完成,没有迭代器。

这是操作顺序:

  1. 我保存指向master item的指针,并将其推送到列表
  2. 我设置了从项的特定指针字段pReferenceItem,使其指向主项
  3. 稍后,我通过列表开始迭代
  4. 我将解析后的信息保存到主条目
  5. 在处理从项时,我尝试访问主项的字段。 但是没变!
  6. 迭代时指向master item的指针和原来的不一样,所以slave item中包含的指针指向原来的(不变)数据

关于我可以使用的一些特殊限定符以强制迭代原始数据的任何建议?我发布列表 and/or 迭代器的方式有什么明显的错误吗?

请注意,我确定上面第 6 步中断言的内容,因为我使用调试器观察了指针值。

这是显示所描述问题的简化代码:

#include <list>
#include <iostream>

std::list<ItemToBeFound> myList;

class ItemToBeFound
{
private:
public:
    /* ... */
    int specialDataToBeRetreived;
    ItemToBeFound *pReferenceItem;
    /* ... */

    ItemToBeFound( ItemToBeFound *ref )
    {
        pReferenceItem = ref;
        specialDataToBeRetreived = 777;
    }

    bool isMaster( void )
    {
        return ( pReferenceItem == NULL );
    }
}


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

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

int main( void )
{
    // ...
    PopulateList();
    // ...

    /* Let's iterate and do something */
    std::list<ItemToBeFound>::iterator it = myList.begin();
    while( it != itemsList.end() )
    {
        if( it->isMaster() )
        {
            it->specialDataToBeRetreived = 42; /* In the real program I get the value after a parsing activity */
        }
        else /* it is a slave item */
        {
            it->specialDataToBeRetreived = it->referenceItem->specialDataToBeRetreived;

            /* But it doesn't work! */
            std::cout << "Updated special value: " << it->specialDataToBeRetreived << std::endl;
        }

        it++;
    }

    // ...

    return 0;
}

在条件的"slave"分支中,在迭代过程中,我期望我在参考对象中设置的specialDataToBeRetreived,但我不断获得777(我在构造函数中设置的示例值):

Updated special value: 777

而我得到这个值的原因是,如调试器所示,迭代阶段的引用项有一个与原始地址不同的地址,由奴隶物品。


问题

迭代器不复制任何东西。在 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() 创建给它的参数的副本(我忽略移动语义 - 这里没有使用它们)。注意参数是什么 - 取消引用 masterItemm1 正如我们之前所说的那样。
push_back 创建的对象 m1 的副本将被调用 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 个对象:m1m2s1s2(均为 ItemToBeFound 类型)
  • 栈上有2个对象:masterItemslaveItem(均为指针类型)
  • 全局区一个对象:myList(这里可以当栈对象)
  • m2s2存储在myList中,可以迭代
  • s1s2 都有指向 m1
  • 的指针
  • 没有从属对象指向m2

稍后,当您 运行 mainFunction() 时,对象 m2 填充了数据,但对象 s2 正在尝试从 m1 检索数据,其中没有填充任何有价值的数据。


如果没有更多信息,很难提出任何解决方案。如果我无法重组 class 结构,我会使用的一种方法是使用 std::list<std::shared_ptr<ItemToBeFound>> 代替,这将确保您的指针安全,并且只将 std::weak_ptr<ItemToBeFound> referenceItem 存储在从属对象中(以避免任何循环依赖并明确声明所有权)。

您可以 std::list<ItemToBeFound*> 并手动管理内存,但这有悖于现代 C++ 精神,应谨慎使用 - 流在 C++ 中比在 C 中需要更多的分支,并且更难跟踪所有他们 delete 一切。

从技术上讲,您可以在从属对象中存储指向存储在列表中的对象的指针(或者甚至是那些对象的迭代器),但这感觉像是一个奇怪的解决方案。您不控制这些对象,也不拥有它们,因此您不应该保留指向这些对象的指针。