【问题标题】:How to get a std::list<T>::iterator from an element of that list?如何从该列表的元素中获取 std::list<T>::iterator?
【发布时间】:2017-12-09 07:34:18
【问题描述】:

给定一个 std:: 列表

std::list< int > myList

以及对该列表中元素的引用(或指针)

int& myElement    |     int* pElement

所以,基本上我知道那个元素的地址

如何有效地为该元素获取std::list&lt;int&gt;::iterator

一个缓慢但有效的例子是

const_iterator it
for( it = myList.begin(); it != &myElement; ++it)
{
    // do nothing, for loop terminates if "it" points to "myElem"
}

有没有更快的方法?喜欢

const_iterator it = magicToIteratorConverter( myList, myElem )

向量案例(但我需要列表):

对于向量,您可以执行以下操作:

const int* pStart = &myVector[0] // address of first element
const int* pElement = &myElem; // address of my element
const idx = static_cast< int >( pElement- pStart ); // no need to divide by size of an elem

std::vector< int >::iterator it = myVector.begin() + idx;

std::list 案例:

【问题讨论】:

  • 您的“缓慢而有效”的示例通常不起作用。
  • std::find 也许?
  • @LightnessRacesinOrbit: v.begin() + (&amp;element - &amp;v[0])
  • @S.H. : 做不到。我建议您首先查看对元素的引用的位置。因为无论它在哪里,它一开始都是一个迭代器。因此,如果您一开始就需要一个迭代器,为什么要丢弃它?
  • 那么你要么重写它,要么坚持使用 O(n) 算法。因为你所要求的是不可能的。

标签: c++ list c++11 stl iterator


【解决方案1】:

给定一个列表,你只能从一端开始遍历它,确保你不会越过一端。

const_iterator it, end;
for( it = myList.begin(), end = myList.end(); it!=end && it != &myElement; ++it)
{
    // do nothing, for loop terminates if "it" points to "myElem"
      // or if we don't find your element.
}

当然,您可以使用标准算法,例如std::find 来查找它。

或者,您可以在插入时保留迭代器,并且在许多情况下它在以后仍然有效。

如果您想要查找速度,您可能应该使用列表以外的其他内容。


如果你有类似的东西

int x = 42;
int * this_might_be_handy = &x;
myList.insert(x);

myList 现在有一个数字的副本 - 它的值是 42,但在不同的内存位置。

如果您在列表中保留指向整数的指针,情况会有所不同。从列表的front 中获取值并查看地址不会给出与x 相同的地址。

但你必须巧妙地管理它们。

【讨论】:

  • 如果我知道元素的地址怎么办?
  • it != &amp;myElement 你确定这行得通吗?我认为迭代器的值不需要是对象的地址。我认为&amp;(*it) != &amp;myElement 会更正确。
  • @S.H:这个答案向您展示了如何从元素的地址获取到节点的迭代器。
  • @NathanOliver: != 但是是的
  • @LightnessRacesinOrbit 我知道我可以使用it != &amp;myElement´, but I can't use std::list::iterator = &myElem´。而这正是我所需要的
【解决方案2】:

std::list&lt;int&gt;::iterator 不是int*,您需要访问迭代器中的元素并获取其地址。此外,std::find_if 会为您处理大部分样板文件。

auto iter = std:find_if(myList.begin(), myList.end(),
                        [&myElement](const int & listElement)
                        { return &myElement == &listElement; });

自己编写循环如下所示:

auto iter = myList.end();

for(auto i = myList.begin(); i != myList.end(); ++i)
    if(&*i == &myElement)
    {
        iter = i;
        break;
    }

【讨论】:

  • 这是一个有趣的问题 - 我们是否绝对保证 listElement 将引用存储在容器中的同一对象?
  • @LightnessRacesinOrbit 我知道这在此处已详细讨论过。我似乎记得结论是标准并不能保证这一点,但是大多数人似乎并不为此烦恼。
  • 刚刚看了 C++14 25.2.5/1,我们似乎是的
  • @LightnessRacesinOrbit 我不确定我是否理解你的问题,因为我看不出你的标准报价如何回答这个问题。
  • 它说:“范围 [first,last) 中的第一个迭代器 i 满足以下相应条件:*i == value, pred(*i) != false, pred(*i) == false""至多最后 - 相应谓词的第一次应用。" 这意味着(我认为)您的谓词的所有调用都必须给出从容器中取消引用迭代器的结果(并且这样的结果始终是对实际元素的引用)。这意味着即使在超级挑剔级别,您的答案也是正确的:恭喜!
【解决方案3】:

这实际上是一个很好的问题,恕我直言,没有标准的方法来做 OP 要求的事情。如果您了解列表节点本质上是这样的

struct list_node {
    list_node* prev;
    list_node* next;
    T yourType;
}

如果你有一个指向yourType 的指针而不搜索整个容器,那么没有默认的方法可以到达该节点(迭代器是一个指向该节点的指针),这很糟糕。


由于 std 不能帮助您,因此您必须弄脏自己的手:

#include <list>
#include <iostream>

//This is essentially what you are looking for:
std::list<int>::iterator pointerToIter (int* myPointer) {
    //Calculates the distance in bytes from an iterator itself
    //to the actual type that is stored at the position the
    //iterator is pointing to.
    size_t iterOffset = (size_t)&(*((std::list<void*>::iterator)nullptr));
    //Subtract the offset from the passed pointer and make an
    //iterator out of it
    std::list<int>::iterator iter;
    *(intptr_t*)&iter = (intptr_t)myPointer - iterOffset;
    //You are done
    return iter;
}

int main () {
    std::list<int> intList;
    intList.push_back (10);
    int* i1 = &intList.back ();
    intList.push_back (20);
    intList.push_back (30);
    int* i3 = &intList.back ();
    intList.push_back (40);
    intList.push_back (50);
    int* i5 = &intList.back ();

    std::cout << "Size: " << intList.size () << " | Content: ";
    for (const int& value : intList)
        std::cout << value << " ";
    std::cout << std::endl;

    intList.erase (pointerToIter (i1));
    intList.erase (pointerToIter (i3));
    intList.erase (pointerToIter (i5));

    std::cout << "Size: " << intList.size () << " | Content: ";
    for (const int& value : intList)
        std::cout << value << " ";
    std::cout << std::endl;
    return 0;
}

输出(证明它按预期工作):

尺寸:5 |内容:10 20 30 40 50
尺寸:2 |内容:20 40

即使 std::list 的实现会为列表节点使用不同的布局或向其添加更多成员,这也能完美运行。我还包含了生成的汇编代码,以查看该函数实质上已简化为 myPointer - 0x100x10 = 16 是 64 位机器上 2 个指针的大小)。

汇编器(至少具有-O1):

std::list<int>::iterator pointerToIter (int* myPointer) {
   0:   48 8d 47 f0             lea    rax,[rdi-0x10]
}
   4:   c3                      ret    

【讨论】:

    【解决方案4】:

    这是上述 Xatian 非常有趣的解决方案的剖析、形式化 (C++11) 和注释版本。该解决方案是 O(1)(这是它如此有趣的原因之一),但它假设了一些应该考虑的事情(Xatian 对列表的 STL 实现的深入了解是它如此有趣的另一个原因)。

    #include <iostream>
    #include <list>
    #include <cstdint>
    
    int main(void)
    {
        // ASSUMPTIONS:
    
        //  1.- nullptr == 0
        //  2.- A std::list iterator is an object that contains just one thing: a pointer to the body of the iterator.
        //  3.- A std::list iterator has a constructor that admits a pointer to a body provided by the user, which creates the iterator with that (assumed) existing body.
    
    
        using DataType = int;
        using DataList = std::list<DataType>;
    
        std::cout << "Nullptr= " << reinterpret_cast<size_t>(nullptr) << std::endl;
        std::cout << "Size of a pointer = " << sizeof(nullptr) << ", size of iterator = " << sizeof(DataList::iterator) << std::endl;
    
        static_assert(reinterpret_cast<size_t>(nullptr) == 0,
                    "In this compiler, nullptr is not 0 and this will not work");
    
    
        // we have a list filled with something.
        DataList mylist{1,2,3,4};
        // and an iterator pointing to some data.
        DataList::iterator itaux{mylist.begin()};
        ++itaux;
        ++itaux;
    
    
        // 1. calculate the offset of the data in a list iterator w.r.t. the beginning of the iterator body
    
        DataList::iterator it{nullptr}; // call the iterator constructor. Nullptr becomes the address of the body where the iterator would store prev/next/data
                                    // since nullptr is assumed to be 0, this is the same as to declare an iterator with its body at 0x00000000
        DataType & itp = *it; // this is a reference to the user's data in the iterator body
                          // that iterator is a fake and does not contain any data, but since we are only dealing with addresses, no problem...
        DataType * aitp = & itp; // this gets the address equivalent to the reference, which is at some point in memory from 0
        size_t iteroffset = reinterpret_cast<size_t>(aitp); // That address becomes, actually, the offset of the data w.r.t. the beginning of the iterator body
    
        std::cout << "Offset from iterator body start to data = " << iteroffset << std::endl;
    
    
        // 2. we can get the pointer to the data from our existing iterator
        DataType * mypointer = &(*itaux);
    
        // 3. we can create a valid iterator from the pointer to the data
        DataList::iterator iter;
        *(reinterpret_cast<intptr_t*>(&iter)) = reinterpret_cast<intptr_t>(mypointer) - iteroffset; // the address of the beginning of the body (mypointer-iteroffset) is stored into 
                                                                                                    // the pointer to the body that the iterator actually is
    
        std::cout << "pointed element: " << (*iter) << std::endl;
    
        return(0);
    }
    

    【讨论】:

      猜你喜欢
      • 2020-12-23
      • 1970-01-01
      • 1970-01-01
      • 2014-03-12
      • 2012-06-16
      • 2016-03-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多