问题
I have this code (roughly) in a cpp app:
QList<Foo> rawAssets = getlist();
QVector<std::pair<QString, QString>> assets;
std::transform(rawAssets.begin(), rawAssets.end(), assets.begin(), makePair);
That causes exc_bad_access
to throw when I use assets
again.
However, if I change assets.begin()
to std::back_inserter(assets)
then it works as I expect.
I found tutorials for std::transform
showing both kinds of usage. Why is it wrong in my case?
回答1:
Since you've just declared QVector<std::pair<QString, QString>> assets
; it's empty. assets.begin()
is therefore equal to assets.end()
, and points past-the-end of the empty vector.
The third parameter of std::transform
is an iterator through which transform
will write the results of the transformation (and increment it after each write).
When you pass in assets.begin()
, transform
will write through this past-the-end iterator, resulting in an out-of-bounds write. It's roughly the same as doing char x[3]; x[4] = 'a';
When you pass in std::back_inserter(assets)
, you create a special iterator such that writing through it actually inserts the written element into assets
. So all is well.
The first form could be used if assets
was already of sufficient size, and you wanted to overwrite the elements in it. The second form is used when you want to extend assets
with the results of the transformation.
回答2:
assets
starts out as an empty vector.
transform()
's third parameter is an output iterator. Your code is essentially equivalent to the following:
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.
That's how transform()
works. Since assets()
is an empty vector, begin()
gives you the ending iterator value, and then the code proceeds to merrily write past the end of the vector, and crap all over itself.
You have two basic options:
Before obtaining the
begin()
iterator,resize()
the vector to the number of elements you're about totransform()
, sotransform()
ends up filling out a pre-resized array's contents, exactly. However, since your data comes from a list, this is not easily available, so:Pass a std::back_insert_iterator to
transform()
, instead of an iterator to the empty array.
回答3:
std::transform()
assumes that it can write directly into the output. Which in your case is a vector of zero size. You can fix the bug by explicitly resizing your vector to the size of the transform input, or by using back_inserter
as you already discovered.
回答4:
If you resize assets
to the same size as rawAssets
before the transform
, it won't cause a bad access. Using a back_insert_iterator
causes push_back
to be called for each iterator of transform
. Without that, it tries to access the first, second, third, etc element of assets
, which is still empty, therefore causing a bad access.
来源:https://stackoverflow.com/questions/42887034/why-does-this-use-of-stdtransform-cause-exc-bad-access