简体   繁体   中英

Why does this use of std::transform cause exc_bad_access

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?

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'; 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.

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:

  1. Before obtaining the begin() iterator, resize() the vector to the number of elements you're about to transform() , so transform() 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:

  2. Pass a std::back_insert_iterator to transform() , instead of an iterator to the empty array.

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM