为什么使用 std::transform 会导致 exc_bad_access
Why does this use of std::transform cause exc_bad_access
我在 cpp 应用程序中(大致)有这段代码:
QList<Foo> rawAssets = getlist();
QVector<std::pair<QString, QString>> assets;
std::transform(rawAssets.begin(), rawAssets.end(), assets.begin(), makePair);
这导致 exc_bad_access
在我再次使用 assets
时抛出。
但是,如果我将 assets.begin()
更改为 std::back_inserter(assets)
,那么它会按我的预期工作。
我发现 std::transform
的教程显示了这两种用法。为什么我的情况不对?
std::transform()
假定它可以直接写入输出。在您的情况下,这是一个零大小的向量。您可以通过将向量的大小显式调整为转换输入的大小,或使用您已经发现的 back_inserter
来修复该错误。
如果您将 assets
的大小调整为与 transform
之前的 rawAssets
相同的大小,则不会导致访问错误。使用 back_insert_iterator
会导致为 transform
的每个迭代器调用 push_back
。否则,它会尝试访问 assets
的第一个、第二个、第三个等元素,它仍然是空的,因此导致访问错误。
assets
开始时是一个空向量。
transform()
的第三个参数是一个输出迭代器。您的代码基本上等同于以下内容:
QVector<std::pair<QString, QString>> assets;
auto output_iter=assets.begin();
// You now call transform(), passing output_iter
//
// transform() then essentially does the following:
*output_iter++ = /* first transform()ed value */;
*output_iter++ = /* second transform()ed value */;
// ... and so on.
这就是 transform()
的工作原理。由于 assets()
是一个空向量,begin()
给你结束迭代器值,然后代码继续快乐地写到向量的末尾,并在其自身上胡扯。
您有两个基本选择:
在获取begin()
迭代器之前,resize()
向量到你要transform()
的元素个数,所以transform()
结束up 准确地填充预先调整大小的数组的内容。但是,由于您的数据来自列表,因此不容易获得,因此:
将 std::back_insert_iterator 传递给 transform()
,而不是将迭代器传递给空数组。
由于您刚刚声明 QVector<std::pair<QString, QString>> assets
;它是 空的。 assets.begin()
因此等于 assets.end()
,并且指向空向量的末尾。
std::transform
的第三个参数是一个迭代器,transform
将通过它 写入 转换的结果(并在每次写入后递增)。
当您传入 assets.begin()
时,transform
将通过这个尾后迭代器进行写入,从而导致越界写入。这与 char x[3]; x[4] = 'a';
大致相同
当您传入 std::back_inserter(assets)
时,您会创建一个特殊的迭代器,这样通过它写入实际上 将写入的元素 插入到 assets
中。所以一切都很好。
如果 assets
已经足够大,并且您想 覆盖 其中的元素,则可以使用第一种形式。当您想要使用转换结果 扩展 assets
时使用第二种形式。
我在 cpp 应用程序中(大致)有这段代码:
QList<Foo> rawAssets = getlist();
QVector<std::pair<QString, QString>> assets;
std::transform(rawAssets.begin(), rawAssets.end(), assets.begin(), makePair);
这导致 exc_bad_access
在我再次使用 assets
时抛出。
但是,如果我将 assets.begin()
更改为 std::back_inserter(assets)
,那么它会按我的预期工作。
我发现 std::transform
的教程显示了这两种用法。为什么我的情况不对?
std::transform()
假定它可以直接写入输出。在您的情况下,这是一个零大小的向量。您可以通过将向量的大小显式调整为转换输入的大小,或使用您已经发现的 back_inserter
来修复该错误。
如果您将 assets
的大小调整为与 transform
之前的 rawAssets
相同的大小,则不会导致访问错误。使用 back_insert_iterator
会导致为 transform
的每个迭代器调用 push_back
。否则,它会尝试访问 assets
的第一个、第二个、第三个等元素,它仍然是空的,因此导致访问错误。
assets
开始时是一个空向量。
transform()
的第三个参数是一个输出迭代器。您的代码基本上等同于以下内容:
QVector<std::pair<QString, QString>> assets;
auto output_iter=assets.begin();
// You now call transform(), passing output_iter
//
// transform() then essentially does the following:
*output_iter++ = /* first transform()ed value */;
*output_iter++ = /* second transform()ed value */;
// ... and so on.
这就是 transform()
的工作原理。由于 assets()
是一个空向量,begin()
给你结束迭代器值,然后代码继续快乐地写到向量的末尾,并在其自身上胡扯。
您有两个基本选择:
在获取
begin()
迭代器之前,resize()
向量到你要transform()
的元素个数,所以transform()
结束up 准确地填充预先调整大小的数组的内容。但是,由于您的数据来自列表,因此不容易获得,因此:将 std::back_insert_iterator 传递给
transform()
,而不是将迭代器传递给空数组。
由于您刚刚声明 QVector<std::pair<QString, QString>> assets
;它是 空的。 assets.begin()
因此等于 assets.end()
,并且指向空向量的末尾。
std::transform
的第三个参数是一个迭代器,transform
将通过它 写入 转换的结果(并在每次写入后递增)。
当您传入 assets.begin()
时,transform
将通过这个尾后迭代器进行写入,从而导致越界写入。这与 char x[3]; x[4] = 'a';
当您传入 std::back_inserter(assets)
时,您会创建一个特殊的迭代器,这样通过它写入实际上 将写入的元素 插入到 assets
中。所以一切都很好。
如果 assets
已经足够大,并且您想 覆盖 其中的元素,则可以使用第一种形式。当您想要使用转换结果 扩展 assets
时使用第二种形式。