从 member-class 调用封装的 std::begin 时程序崩溃
Program crashes, when calling capsulated std::begin from member-class
我的生产代码崩溃了,我找不到原因,
所以我寻找罪恶的根源,现在成功地构建了一个最小的例子,其中变体 B 重现了错误。
也许有人可以帮助我了解这里的问题是什么?
为什么变体 B 会崩溃而变体 A 不会?对我来说,这两个变体似乎应该具有相同的行为?
visual studio 2017 调试器的输出:
void _Adopt(const _Container_base12 *_Parent) _NOEXCEPT
{ // adopt this iterator by parent
if (_Parent == 0)
{ // no future parent, just disown current parent
#if _ITERATOR_DEBUG_LEVEL == 2
_Lockit _Lock(_LOCK_DEBUG);
_Orphan_me();
#endif /* _ITERATOR_DEBUG_LEVEL == 2 */
}
else
{ // have a parent, do adoption
_Container_proxy *_Parent_proxy = _Parent->_Myproxy;
#if _ITERATOR_DEBUG_LEVEL == 2
if (_Myproxy != _Parent_proxy)
{ // change parentage
_Lockit _Lock(_LOCK_DEBUG);
_Orphan_me();
_Mynextiter = _Parent_proxy->_Myfirstiter; <--- MARKED HERE
_Parent_proxy->_Myfirstiter = this;
_Myproxy = _Parent_proxy;
}
#else /* _ITERATOR_DEBUG_LEVEL == 2 */
_Myproxy = _Parent_proxy;
#endif /* _ITERATOR_DEBUG_LEVEL == 2 */
}
}
最小的可编译示例:
#include <vector>
using namespace std;
template <class T>
struct IteratorCapsule
{
const T * m_ptrObj;
IteratorCapsule(const T &refObj) : m_ptrObj(&refObj) {}
typename T::const_iterator begin() const { return std::begin(*m_ptrObj); }
typename T::const_iterator end() const { return std::end(*m_ptrObj); }
};
struct Element
{
vector<int> m_attr;
IteratorCapsule<vector<int>> m_attr_iter_capsule;
Element() : m_attr_iter_capsule(m_attr) {}
const IteratorCapsule<vector<int>> &getAttributes() const
{
return m_attr_iter_capsule;
}
};
struct Config
{
vector<Element> m_element_pool;
Config() { m_element_pool.push_back(Element()); }
};
int main()
{
//variant A
Config oConfigA;
IteratorCapsule<vector<int>> oIterCapsule(oConfigA.m_element_pool[0].m_attr);
auto iterBeginA = oIterCapsule.begin();
//variant B -> crash
Config oConfigB;
auto iterBeginB = oConfigB.m_element_pool[0].getAttributes().begin();
return 0;
}
让我们尝试显式实现 Element(Element&& x)
看看会发生什么。
Element(Element&& x) : m_attr (std::move(x.m_attr)),
m_attr_iter_capsule (std::move(x.m_attr_iter_capsule)) // WRONG
{}
当(但不仅限于)Element
从右值构造时调用,例如在本例中:
Element a {std::move(Element())};
在这种情况下 a.m_attr_iter_capsule
将包含指向内部 Element()
的指针(超出该行代码之后的范围)
(请注意,取消引用越界指针是未定义的行为。不保证它会发生段错误)
在 OP 的代码中,这一行
m_element_pool.push_back(Element());
调用 Element(Element&&)
。问题可以是 "silenced" 通过使用 emplace_back()
(但这不是这个答案的重点)。
要修复它,只需定义显式复制和移动构造函数,并适当地初始化 m_attr_iter_capsule
;或使用 = delete
.
删除那些构造函数
最后,这里是带有显式移动构造函数和删除的复制赋值运算符、移动赋值运算符、复制构造函数的工作解决方案。
#include <iostream>
#include <vector>
template <class T>
struct IteratorCapsule
{
const T * m_ptrObj;
IteratorCapsule(const T &refObj) : m_ptrObj(&refObj) {}
typename T::const_iterator begin() const { return std::begin(*m_ptrObj); }
typename T::const_iterator end() const { return std::end(*m_ptrObj); }
};
struct Element
{
std::vector<int> m_attr;
IteratorCapsule<std::vector<int>> m_attr_iter_capsule;
Element(const Element&) = delete;
Element& operator=(const Element&) = delete;
Element& operator=(Element&&) = delete;
explicit Element() noexcept : m_attr_iter_capsule(m_attr) { m_attr.push_back(666); }
Element(Element&& x) noexcept : m_attr(std::move(x.m_attr)), m_attr_iter_capsule(m_attr) {}
~Element() noexcept {};
const IteratorCapsule<std::vector<int>> &getAttributes() const
{
return m_attr_iter_capsule;
}
};
struct Config
{
std::vector<Element> m_element_pool;
Config() { m_element_pool.push_back(Element()); }
};
int main()
{
//variant A
Config oConfigA;
IteratorCapsule<std::vector<int>> oIterCapsule(oConfigA.m_element_pool[0].m_attr);
auto iterBeginA = oIterCapsule.begin();
std::cout << "Variant A: " << *iterBeginA << std::endl;
//variant B -> works now!
Config oConfigB;
auto iterBeginB = oConfigB.m_element_pool[0].getAttributes().begin();
std::cout << "Variant B: " << *iterBeginB << std::endl;
return 0;
}
我的生产代码崩溃了,我找不到原因, 所以我寻找罪恶的根源,现在成功地构建了一个最小的例子,其中变体 B 重现了错误。
也许有人可以帮助我了解这里的问题是什么?
为什么变体 B 会崩溃而变体 A 不会?对我来说,这两个变体似乎应该具有相同的行为?
visual studio 2017 调试器的输出:
void _Adopt(const _Container_base12 *_Parent) _NOEXCEPT
{ // adopt this iterator by parent
if (_Parent == 0)
{ // no future parent, just disown current parent
#if _ITERATOR_DEBUG_LEVEL == 2
_Lockit _Lock(_LOCK_DEBUG);
_Orphan_me();
#endif /* _ITERATOR_DEBUG_LEVEL == 2 */
}
else
{ // have a parent, do adoption
_Container_proxy *_Parent_proxy = _Parent->_Myproxy;
#if _ITERATOR_DEBUG_LEVEL == 2
if (_Myproxy != _Parent_proxy)
{ // change parentage
_Lockit _Lock(_LOCK_DEBUG);
_Orphan_me();
_Mynextiter = _Parent_proxy->_Myfirstiter; <--- MARKED HERE
_Parent_proxy->_Myfirstiter = this;
_Myproxy = _Parent_proxy;
}
#else /* _ITERATOR_DEBUG_LEVEL == 2 */
_Myproxy = _Parent_proxy;
#endif /* _ITERATOR_DEBUG_LEVEL == 2 */
}
}
最小的可编译示例:
#include <vector>
using namespace std;
template <class T>
struct IteratorCapsule
{
const T * m_ptrObj;
IteratorCapsule(const T &refObj) : m_ptrObj(&refObj) {}
typename T::const_iterator begin() const { return std::begin(*m_ptrObj); }
typename T::const_iterator end() const { return std::end(*m_ptrObj); }
};
struct Element
{
vector<int> m_attr;
IteratorCapsule<vector<int>> m_attr_iter_capsule;
Element() : m_attr_iter_capsule(m_attr) {}
const IteratorCapsule<vector<int>> &getAttributes() const
{
return m_attr_iter_capsule;
}
};
struct Config
{
vector<Element> m_element_pool;
Config() { m_element_pool.push_back(Element()); }
};
int main()
{
//variant A
Config oConfigA;
IteratorCapsule<vector<int>> oIterCapsule(oConfigA.m_element_pool[0].m_attr);
auto iterBeginA = oIterCapsule.begin();
//variant B -> crash
Config oConfigB;
auto iterBeginB = oConfigB.m_element_pool[0].getAttributes().begin();
return 0;
}
让我们尝试显式实现 Element(Element&& x)
看看会发生什么。
Element(Element&& x) : m_attr (std::move(x.m_attr)),
m_attr_iter_capsule (std::move(x.m_attr_iter_capsule)) // WRONG
{}
当(但不仅限于)Element
从右值构造时调用,例如在本例中:
Element a {std::move(Element())};
在这种情况下 a.m_attr_iter_capsule
将包含指向内部 Element()
的指针(超出该行代码之后的范围)
(请注意,取消引用越界指针是未定义的行为。不保证它会发生段错误)
在 OP 的代码中,这一行
m_element_pool.push_back(Element());
调用 Element(Element&&)
。问题可以是 "silenced" 通过使用 emplace_back()
(但这不是这个答案的重点)。
要修复它,只需定义显式复制和移动构造函数,并适当地初始化 m_attr_iter_capsule
;或使用 = delete
.
最后,这里是带有显式移动构造函数和删除的复制赋值运算符、移动赋值运算符、复制构造函数的工作解决方案。
#include <iostream>
#include <vector>
template <class T>
struct IteratorCapsule
{
const T * m_ptrObj;
IteratorCapsule(const T &refObj) : m_ptrObj(&refObj) {}
typename T::const_iterator begin() const { return std::begin(*m_ptrObj); }
typename T::const_iterator end() const { return std::end(*m_ptrObj); }
};
struct Element
{
std::vector<int> m_attr;
IteratorCapsule<std::vector<int>> m_attr_iter_capsule;
Element(const Element&) = delete;
Element& operator=(const Element&) = delete;
Element& operator=(Element&&) = delete;
explicit Element() noexcept : m_attr_iter_capsule(m_attr) { m_attr.push_back(666); }
Element(Element&& x) noexcept : m_attr(std::move(x.m_attr)), m_attr_iter_capsule(m_attr) {}
~Element() noexcept {};
const IteratorCapsule<std::vector<int>> &getAttributes() const
{
return m_attr_iter_capsule;
}
};
struct Config
{
std::vector<Element> m_element_pool;
Config() { m_element_pool.push_back(Element()); }
};
int main()
{
//variant A
Config oConfigA;
IteratorCapsule<std::vector<int>> oIterCapsule(oConfigA.m_element_pool[0].m_attr);
auto iterBeginA = oIterCapsule.begin();
std::cout << "Variant A: " << *iterBeginA << std::endl;
//variant B -> works now!
Config oConfigB;
auto iterBeginB = oConfigB.m_element_pool[0].getAttributes().begin();
std::cout << "Variant B: " << *iterBeginB << std::endl;
return 0;
}