列表项和迭代器:是推回还是迭代器创建原始数据的副本?
List items and iterators: do either push back or iterators create copies of original data?
虽然我有很多C Embedded Programmer 的经验,但实际上我对C++ 不是很熟练。我正在实现一个解析器,其中 ItemsToBeFound
被推入列表。
碰巧其中一些项目(比如说主项目)携带更多信息,因此其他一些项目(从项目 ) 需要参考 主条目 来检索这些信息。 link 在填充阶段完成,没有迭代器。
这是操作顺序:
- 我保存指向master item的指针,并将其推送到列表
- 我设置了从项的特定指针字段
pReferenceItem
,使其指向主项
- 稍后,我通过列表开始迭代
- 我将解析后的信息保存到主条目
- 在处理从项时,我尝试访问主项的字段。 但是没变!
- 迭代时指向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
而我得到这个值的原因是,如调试器所示,迭代阶段的引用项有一个与原始地址不同的地址,由奴隶物品。
问题
- 为什么会发生这种变化?
- 我原以为指向我的对象的指针只是 复制 到列表,就像我见过的许多 list 实现一样在我生命中。
std::list
模板的 push_back
方法是否执行指向对象的复制?
迭代器不复制任何东西。在 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
正如我们之前所说的那样。
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 个对象:
m1
、m2
、s1
和 s2
(均为 ItemToBeFound
类型)
- 栈上有2个对象:
masterItem
和slaveItem
(均为指针类型)
- 全局区一个对象:
myList
(这里可以当栈对象)
m2
和s2
存储在myList
中,可以迭代
s1
和 s2
都有指向 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
一切。
从技术上讲,您可以在从属对象中存储指向存储在列表中的对象的指针(或者甚至是那些对象的迭代器),但这感觉像是一个奇怪的解决方案。您不控制这些对象,也不拥有它们,因此您不应该保留指向这些对象的指针。
虽然我有很多C Embedded Programmer 的经验,但实际上我对C++ 不是很熟练。我正在实现一个解析器,其中 ItemsToBeFound
被推入列表。
碰巧其中一些项目(比如说主项目)携带更多信息,因此其他一些项目(从项目 ) 需要参考 主条目 来检索这些信息。 link 在填充阶段完成,没有迭代器。
这是操作顺序:
- 我保存指向master item的指针,并将其推送到列表
- 我设置了从项的特定指针字段
pReferenceItem
,使其指向主项 - 稍后,我通过列表开始迭代
- 我将解析后的信息保存到主条目
- 在处理从项时,我尝试访问主项的字段。 但是没变!
- 迭代时指向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
而我得到这个值的原因是,如调试器所示,迭代阶段的引用项有一个与原始地址不同的地址,由奴隶物品。
问题
- 为什么会发生这种变化?
- 我原以为指向我的对象的指针只是 复制 到列表,就像我见过的许多 list 实现一样在我生命中。
std::list
模板的push_back
方法是否执行指向对象的复制?
迭代器不复制任何东西。在 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
正如我们之前所说的那样。
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 个对象:
m1
、m2
、s1
和s2
(均为ItemToBeFound
类型) - 栈上有2个对象:
masterItem
和slaveItem
(均为指针类型) - 全局区一个对象:
myList
(这里可以当栈对象) m2
和s2
存储在myList
中,可以迭代s1
和s2
都有指向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
一切。
从技术上讲,您可以在从属对象中存储指向存储在列表中的对象的指针(或者甚至是那些对象的迭代器),但这感觉像是一个奇怪的解决方案。您不控制这些对象,也不拥有它们,因此您不应该保留指向这些对象的指针。