c++ - 移动构造对象的 GCC 奇怪行为

标签 c++ c++11 gcc g++ clang++

不幸的是,我无法降低这个问题的复杂性,所以它的代码相当长。问题的核心是我正在运行的测试。

TEST(Iterator, enumerator_rvalue)
{
  std::size_t index1 = 5;
  for (auto item : nr::enumerate(nr::range<std::size_t>(5, 27))) {
    ASSERT_EQ(index1 - 5, item.index);
    ASSERT_EQ(index1++, item.value);
  }
  ASSERT_EQ(index1, 27);
}

当使用 GCC 5 和 6 编译时,断言在 for 循环的第二次迭代中失败,但适用于 Clang 3.6、3.7、3.8(在 Travis CI 上测试)和 MSVC 2015(在AppVeyor ,测试用例名称为Iterator.enumerator_rvalue).

我已将所有必要的代码复制到一个文件中以在 Coliru 上复制它,并添加了一些小打印和 ASSERT_EQ() 的临时实现


描述代码的作用:nr::range 是一个迭代器,它只是从start 迭代到end-1nr::enumerate() 生成一个 nr::enumerator,它接受 Iterable右值。因此在这种情况下,nr::range 对象将移动nr::enumerator::iter 成员。

nr::enumerate(nr::range<std::size_t>(5, 27))

nr::enumerator 现在简单地将迭代器和 nr::range 产生的值包装在 nr::enumerator::item 对象将其与索引相关联。

这些是在 Clang 的 for 循环中打印的前两行:

index1=5, item.index:0, item.value:5
index1=6, item.index:1, item.value:6

对于 GCC,它是

index1=5, item.index:0, item.value:5
index1=6, item.index:1, item.value:1

现在 IMO 最有趣的部分是,当我注释(又名删除)ASSERT_EQ() 语句时,GCC 也打印出正确的值

index1=5, item.index:0, item.value:5
index1=5, item.index:1, item.value:6

现在问题:这是 GCC 错误还是我触发了未定义的行为?

我也考虑过我自己的 ASSERT_EQ() 宏变得疯狂的可能性,但我找不到它的问题,而且这也不能解释 googletest 的失败Travis 和 AppVeyor。


编辑:我将在此处包含代码以防 Coliru 链接中断 (@NathanOliver)。只需复制并粘贴到文件中并执行 (g|clang)++ -std=c++(11|14) main.cpp && ./a.out

#include <iostream>

// Copyright (c) 2016  Niklas Rosenstein
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// @file nr/iterator.h
// @created 2016-01-21
// @lastmodified 2016-06-06

//#pragma once
#include <cassert>

// ---------------------------------------------------------------------------
// @macro NR_ITERATOR_NOTYPETRAITS
//
// If this macro is defined, #<type_traits> will not but used. This requires
// the implementor of the #nr::iterator interface to manually specify the
// datatype that the `operator * ()` (dereference operator) will return
// as `yield_type`.
// ---------------------------------------------------------------------------
#ifndef NR_ITERATOR_NOTYPETRAITS
  #include <type_traits>
#endif


namespace nr {

// ---------------------------------------------------------------------------
// The #self_iterator is an adapter class from a new style iterator
// interface to the C++ iterator interface. It can be subclassed to
// implement iterator objects with the following interface:
//
// - `at_end() const`
// - `next()`
// - `X operator * () const`
//
// The first two methods will be used by the #self_iterator methods
// #operator++(), #operator!=() and #operator bool() to adapt to the
// C++ iterator interface. The dereference operator is required by
// the C++ iterator interface directly.
//
// This class should be used when implementing a simple iterator.
// However, the calls to #begin() and #end() will cause a copy of
// the iterator to be created, thus it will be problematic if your
// iterator allocates and frees resources automatically.
//
// For such cases, use the #iterator instead.
// ---------------------------------------------------------------------------
template <class T>
class self_iterator
{
public:

  // -------------------------------------------------------------------------
  // Returns false if the iterator is at its end, true otherwise.
  // This method calls #at_end(). Note that #at_end() must be declared
  // `const` if this operator is used.
  // -------------------------------------------------------------------------
  inline operator bool () const {
    return !static_cast<T const*>(this)->at_end();
  }

  // -------------------------------------------------------------------------
  // Used by the standard C++ iterator interface. Calls `at_end()` to
  // check if the end of the iterator was reached.
  // -------------------------------------------------------------------------
  inline bool operator != (self_iterator const&) {
    return !static_cast<T*>(this)->at_end();
  }

  // -------------------------------------------------------------------------
  // Increment operator. Calls `next()` to advance to the next element.
  // -------------------------------------------------------------------------
  self_iterator& operator ++ () {
    static_cast<T*>(this)->next();
    return *this;
  }

  // -------------------------------------------------------------------------
  // C++ iterator interface.
  // -------------------------------------------------------------------------
  // @{
  T& begin() { return *static_cast<T*>(this); }
  T& end() { return *static_cast<T*>(this); }
  // @}

};

// ---------------------------------------------------------------------------
// This class represents an adapter of the C++ iterator interface
// for new style iterator classes.
// ---------------------------------------------------------------------------
template <typename T>
class iterator_adapter
{

  // -------------------------------------------------------------------------
  // Pointer to the iterator implementation.
  // -------------------------------------------------------------------------
  T* itimpl;

public:

  // -------------------------------------------------------------------------
  // @typedef yield_type
  //
  // This type represents the datatype that is yielded by the iterator.
  // If #NR_ITERATOR_NOTYPETRAITS is defined, #<type_traits> will not be
  // used and #T must provide the #yield_type instead.
  // -------------------------------------------------------------------------
  #ifdef NR_ITERATOR_NOTYPETRAITS
    typedef typename T::yield_type yield_type;
  #else
    typedef typename std::result_of<decltype(&T::operator *)(T)>::type yield_type;
  #endif

  // -------------------------------------------------------------------------
  // Passing nullptr to this constructor represents creates a
  // sentinel iterator that marks the end of the iteration.
  // -------------------------------------------------------------------------
  inline iterator_adapter(T* itimpl) : itimpl(nullptr)
  {
    if (itimpl && !itimpl->at_end()) {
      this->itimpl = itimpl;
    }
  }

  // -------------------------------------------------------------------------
  // Calls `next()` and `at_end()` on the iterator implementation
  // to advance to the next item and check if the end of the iteration
  // has been reached.
  // -------------------------------------------------------------------------
  inline iterator_adapter& operator ++ ()
  {
    assert(this->itimpl != nullptr);
    this->itimpl->next();
    if (this->itimpl->at_end()) {
      this->itimpl = nullptr;
    }
    return *this;
  }

  // -------------------------------------------------------------------------
  // -------------------------------------------------------------------------
  inline bool operator != (iterator_adapter const& other) const
  {
    return this->itimpl != other.itimpl;
  }

  // -------------------------------------------------------------------------
  // -------------------------------------------------------------------------
  inline yield_type operator * () const
  {
    assert(this->itimpl != nullptr);
    return this->itimpl->operator * ();
  }

};


// ---------------------------------------------------------------------------
// This is an extension of the #self_iterator that uses an
// #iterator_adapter class for #begin() and #end() instead.
// It should be used when employing an iterator that allocates and
// frees resource dynamically.
// ---------------------------------------------------------------------------
template <class T>
class iterator : public self_iterator<T>
{
public:

  // -------------------------------------------------------------------------
  // Returns the #iterator_adapter for this iterator implementation.
  // It does not really mark the begin of the iteration, rather the
  // current state of the iterator.
  // -------------------------------------------------------------------------
  inline iterator_adapter<T> begin() { return iterator_adapter<T>(static_cast<T*>(this)); }

  // -------------------------------------------------------------------------
  // Returns the sentinel #iterator_adapter that marks the end of
  // the iteration.
  // -------------------------------------------------------------------------
  inline iterator_adapter<T> end() const { return iterator_adapter<T>(nullptr); }

  // -------------------------------------------------------------------------
  // Returns false if the iterator is at its end, true otherwise.
  // This method calls #at_end(). Note that #at_end() must be declared
  // `const` if this operator is used.
  // -------------------------------------------------------------------------
  inline operator bool () const {
    return !static_cast<T const*>(this)->at_end();
  }

};

} // namespace nr
// Copyright (c) 2016  Niklas Rosenstein
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// @file <nr/iterator/range.h>
// @created 2016-07-02
// @brief Provides the #nr::range iterator class.

//#pragma once
//#include "../iterator.h"
#include <iterator>

namespace nr {

// ---------------------------------------------------------------------------
// An implementation of a range iterator using the #nr::iterator
// interface. Supports an arbitrary integer dataype that can be
// specified with the template parameter #T.
// ---------------------------------------------------------------------------
template <typename T>
class range : public iterator<range<T>>
{
  T curr, max, step;

  // -------------------------------------------------------------------------
  // Simple function to retrieve the sign of a function.
  // -------------------------------------------------------------------------
  static int sign(T val) { return (T(0) < val) - (val < T(0)); }

public:

  // -------------------------------------------------------------------------
  // Create a range iterator starting from zero up excluding #max.
  // -------------------------------------------------------------------------
  inline range(T max) : curr(0), max(max), step(1) { std::cout << "range()\n"; }

  // -------------------------------------------------------------------------
  // Create a range iterator starting from #min up to excluding #max
  // taking the specified #step each turn (must not be zero and the
  // sign must match the direction of the iteration).
  // -------------------------------------------------------------------------
  inline range(T min, T max, T step = 1) : curr(min), max(max), step(step)
  {
    assert(step != 0 && sign(step) == sign(max - min));
    std::cout << "range()\n"; 
  }



  inline ~range() {
      std::cout << "~range()\n"; 
  }

public:  // nr::iterator overrides

  inline bool at_end() const { return this->curr >= this->max; }

  inline void next() { this->curr += step; }

  inline T operator * () const { return this->curr; }
};

} // namespace nr




// Copyright (c) 2016  Niklas Rosenstein
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
/*!
* @file <nr/iterator/enumerate.h>
* @created 2016-12-08
* @brief Python's `enumerate()` like iterator.
*/

//#pragma once
//#include "../iterator.h"

namespace nr {

  /*!
  * Implementation of the iterator behind #enumerate().
  */
  template <class Iterable>
  class enumerator : public nr::iterator<enumerator<Iterable>>
  {
    Iterable iter;
    std::size_t index;
    decltype(std::begin(iter)) iter_begin;
    decltype(std::end(iter)) const iter_end;

  public: // types

    struct item
    {
      std::size_t index;
      decltype(*iter_begin) value;
    };

  public:

    enumerator(Iterable&& iter_)
    : iter(std::move(iter_)), index(0), iter_begin(std::begin(iter_))
    , iter_end(std::end(iter_)) {}

    bool at_end() const { return !(this->iter_begin != this->iter_end); }

    void next() { ++this->iter_begin; ++this->index; }

    item operator * () const { return {this->index, *this->iter_begin}; }

  };

  /*!
  * Helper for lvalues passed to #enumerate().
  */
  template <typename Iterable>
  class enumerator_lvalue_wrapper
  {
    Iterable& iter;
  public:
    enumerator_lvalue_wrapper(Iterable& iter_) : iter(iter_) {}
    enumerator_lvalue_wrapper(enumerator_lvalue_wrapper&& other)
      : iter(other.iter) {}

    auto begin() -> decltype(std::begin(iter)) { return std::begin(iter); }
    auto end() -> decltype(std::end(iter)) { return std::end(iter); }

    auto begin() const -> decltype(std::begin(iter)) { return std::begin(iter); }
    auto end() const -> decltype(std::end(iter)) { return std::end(iter); }
  };


  /*!
  * Enumerate over an iterator.
  */
  template <typename Iterable>
  auto enumerate(Iterable& iter) -> enumerator<enumerator_lvalue_wrapper<Iterable>>
  {
      std::cout << "enumerate(ref)\n";
    return {enumerator_lvalue_wrapper<Iterable>(iter)};
  }

  /*!
  * Enumerate over an rvalue iterator.
  */
  template <typename Iterable>
  auto enumerate(Iterable&& iter) -> enumerator<Iterable>
  {
      std::cout << "enumerate(move)\n";
    return {std::move(iter)};
  }

} // namespace nr



template <typename T>
static void _assert_eq(T const& a, T const& b, char const* msg) {
  if (a != b) {
    std::cerr << msg << std::endl;
    std::cerr << a << "!=" << b << std::endl;
    throw std::runtime_error("bad");
  }
}

#define ASSERT_EQ(a, b) _assert_eq<decltype(a)>((a), (b), #a " == " #b)

//TEST(Iterator, enumerator_rvalue)
int main()
{
  std::size_t index1 = 5;
  for (auto item : nr::enumerate(nr::range<std::size_t>(5, 27))) {
    std::cout << "index1=" << index1 << ", item.index:" << item.index << ", item.value:" << item.value << std::endl;
    ASSERT_EQ(index1 - 5, item.index);
    ASSERT_EQ(index1++, item.value);
  }
  ASSERT_EQ(index1, 27);
}

最佳答案

问题出在您的 enumerator 构造函数中。

enumerator(Iterable&& iter_)
    : iter(std::move(iter_)), index(0), iter_begin(std::begin(iter_))
    , iter_end(std::end(iter_)) {}

在这里,您将迭代器 iter_beginiter_end 设置为 iter_ 参数,但在外部它是一个临时的初始化后直接过期。这就是导致崩溃的原因。相反,您应该将迭代器设置为 iter 数据成员,这是一个具有移动状态的新对象:

enumerator(Iterable&& iter_)
    : iter(std::move(iter_)), index(0), iter_begin(std::begin(iter))
    , iter_end(std::end(iter)) {}

您的程序现在运行完成:http://coliru.stacked-crooked.com/a/2ddf4ace518ac68e

关于c++ - 移动构造对象的 GCC 奇怪行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41048423/

相关文章:

c++ - 为只接受函数的函数定义接口(interface)

c++ - 如何使用可变参数模板实现折叠

c++ - 为什么 std::hash<int> 似乎是恒等函数

在预处理器指令中组合 ## 和 _type

c++ - 使用共享库的 MySql UDF 不会加载

c++ - 创建图c++的逻辑

c++ - 用 C++ 编写扑克手数计算器

c++ - 如何逐步查看宏扩展?

c++ - 从串行设备上读取字节(并理解它们??)

c++ - 为什么打开 QMessageBox 时 Irrlicht 窗口变黑?