【问题标题】:Why this code does not work as expected?为什么这段代码不能按预期工作?
【发布时间】:2017-06-08 13:00:29
【问题描述】:

以下代码在 Microsoft Visual Studio 2015 中不起作用:

#include <vector>
#include <list>
#include <iostream>

class ListWithIterator
{
public:
    ListWithIterator() : m_iterator(m_list.end()) {}

    bool check() const { return m_iterator == m_list.end(); }

private:
    typedef std::list<int> list_t;

    list_t m_list;
    list_t::const_iterator m_iterator;
};

int main(int, char**)
{
    std::vector<ListWithIterator> v;
    v.resize(1);
    if (v[0].check())
    {
        std::cerr << "Yes" << std::endl;
    }
    else
    {
        std::cerr << "No" << std::endl;
    }
    return 0;
}

MSVC 说:

check() 函数内的“调试断言失败”、“表达式:列表迭代器不兼容”。

g++ 编译它没有任何警告,但它工作错误:

$ g++ sample.cpp -g -Og -Wall
$ ./a.out
No
$

我希望输出“是”,因为迭代器由 m_list.end() 初始化,但 bool check() const { return m_iterator == m_list.end(); } 返回 false

更新:

正确的解决方案:

$ cat sample.cpp
#include <vector>
#include <list>
#include <iostream>

class ListWithIterator
{
public:
    ListWithIterator() : m_iterator(m_list.end()) {}
    ListWithIterator(const ListWithIterator& from): m_list(from.m_list), m_iterator(m_list.end())
    {
    }

    bool check() const
    {
        std::cerr << m_list.size() << std::endl;
        return m_iterator == m_list.end();
    }

private:
    typedef std::list<int> list_t;

    list_t m_list;
    list_t::iterator m_iterator;
};

int main(int, char**)
{
    std::vector<ListWithIterator> v;
    v.resize(1);
    if (v[0].check())
    {
        std::cerr << "Yes" << std::endl;
    }
    else
    {
        std::cerr << "No" << std::endl;
    }
    return 0;
}
$ g++ sample.cpp -g -Og -Wall
$ ./a.out
0
Yes
$

【问题讨论】:

  • 我认为问题在于您将const_iteratorcheck() 中的iterator 进行比较。
  • 是的。将日志放入pastebin:pastebin.com/FDziGfMm
  • 不,即使我将const_iterator 更改为iterator 也不起作用。
  • 使用新的解决方案,如果这很重要,您会在复制过程中丢失迭代器的位置。我的意思是,如果 m_iterator 没有指向 end(),则不会保留在复制构造函数中。
  • 是的,我明白了。对我来说没关系。

标签: c++ list vector iterator


【解决方案1】:

您正在编译时没有-std=c++11 标志,并且只有一个pre-C++11 overload

void resize( size_type count, T value = T() );

如果当前大小小于 count,则附加元素并使用 value 的副本进行初始化。


现在,默认的复制构造函数复制构造两个成员数据,因此您最终会得到一个指向旧(已破坏)列表的迭代器,您稍后会compare with an end iterator of a different list instance,导致未定义的行为。

在 C++11 下编译会定义行为(将选择使用默认构造函数的重载),但修复损坏的复制语义符合您自己的利益。

【讨论】:

  • 对于 Visual Studio,我不确定可能(或曾经?)是什么问题。 VS2015 应该有那个单参数过载。它似乎也有效吗?
【解决方案2】:

您尚未定义复制构造函数,list::resize 插入了默认构造对象的副本。
此副本包含一个迭代器,它引用原始对象中的列表,而不是副本中的列表。

您需要像对待指针一样小心使用迭代器。

【讨论】:

    【解决方案3】:

    如果您触发ListWithIterator 的隐式复制构造函数,那么您最终会得到列表的副本 和迭代器的副本。但是,复制的迭代器仍然引用 original 列表。不同包含的迭代器的比较未定义。

    我看不到您的 ListWithIterator 会被复制到哪里,但来自 MSVC 的消息表明它正在被复制。

    【讨论】:

      猜你喜欢
      • 2018-07-20
      • 2015-06-09
      • 1970-01-01
      • 2016-11-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-10
      相关资源
      最近更新 更多