简体   繁体   中英

Access violation reading - vector of string pointers to value in vector of strings

I'm not very experienced programmer in C++ and I have a problem which I can't resolve. The project on which I'm working is quite big so I can't post here all codes. It is too much code and too much explanation. I write just little part of code, the part which causes me problem, so I hope it is enough. Sorry for the long of my question but I want explain all posted code. Maybe this part of code isn't enough to solve the problem but I want to try it.

First I have a struct called "record":

struct record {
    vector<string> dataRow;
    vector<string *> keys;
    vector<string *> values;

    void setDataRow(vector<string> r) {
         dataRow = r;
    }
}

Some of string data are marked as keys and others as values. I next processing is better for me to have all string data in one vector, so that's the reason why I don't have two vectors of string (vector keys, vector values).

Then I have this:

vector< vector<record> > resultSet;

vector is like data table - set of lines with string data. I need specific count of these tables, therefore vector of vectors of records. The count of tables is optional, so when I set table count I prepare tables by reserve function:

resultSet.reserve(count);
for(unsigned int i = 0; i < count; i++) {
    vector<record> vec;
    resultSet.push_back(vec);
}

When I want add new record to resultSet I know the number of table to which I need insert record. After resultSet[number].push_back(rec) I need change pointers in vectors "keys" and "values" because push_back() creates new copy of "rec" with values of "dataRow" in other memory addresses, right? So I have this function which does push_back and updates pointers:

void insert(int part, vector<string> & dataRow) {
    record r;
    r.setDataRow(dataRow);

    resultSet[part].push_back(r);
    int pos = resultSet.size() - 1; // position of last record
    resultSet[part].at(pos).values.clear();
    resultSet[part].at(pos).keys.clear();

    for(unsigned int i = 0; i < dataRow.size(); i++) {
        record * newRec = &resultSet[part].at(pos);
        if(isValue(dataRow[i])) {
            newRec->values.push_back(&(newRec->dataRow.at(i)));
            // control cout...
        } else {
            newRec->keys.push_back(&(newRec->dataRow.at(i)));
            // control cout...
        }
    }
}

This is working. After push_back in newRec I did control cout of inserted pointers and their referenced values, and everything was ok.

But! After some inserts I call function processData(resultSet), which has to process all data in resultSet. Before implementing processing od data I just wanted print all keys for control to find out if everything is alright. This code:

for(unsigned int i = 0; i < resultSet.size(); i++) {
    for(unsigned int j = 0; j < resultSet[i].size(); j++) {
        cout << "keys: ";
        for(unsigned int k = 0; k < resultSet[i].at(j).keys.size(); k++) {
            cout << *resultSet[i].at(j).keys.at(k) << ", ";
        }
        cout << endl;
    }
}

is bad (Same problem with printing values vector of record). It throws exception of Access violation reading. I know that this exception is thrown when I want to read unaccessible memory, right? Please, tell me that I have mistake in code written above because I really don't know why it doesn't work. Before processing resultSet I do nothing with resultSet except some count of inserts.

Thank you for reading and possible answers.

When you add an entry to a std::vector , all existing pointers to elements in that vector should be considered invalid.

Here is the code that is going wrong.

vector<string> dataRow;
vector<string *> keys;
vector<string *> values;

If keys and values point to the strings in dataRow they will become invalid when dataRow grows.

If I have understood your question correctly, the reason for all this is a fundamental misconception in the way vectors behave .

Your code stores pointers in a vector that points to memory locations allocated by another vector. That would be fine if the vectors didn't change .

The reason for this is that a std::vector is a container that makes a guarantee - all the data it contains will be allocated in a contiguous block of memory.

Now, if you insert an element into a vector, it may move memory locations around. Hence, one of the things you should know is that iterators need to be considered invalid when a vector changes. Iterators are sort of a generalized pointer. In other words, pointers to the locations of elements inside a vector become invalid too.

Now, let's say you updated all your pointers, everywhere, when any of the vectors involved changed. You would then be fine. However, you've now got a bit of an uphill battle on your hands.

As you've said in your comments, you're using pointers because you want efficiency. Your struct is essentially a collection of three strings. Instead of using your own struct, typedef a std::tuple (you will need a C++11 compiler) of 3 std::strings.

Finally, when you need to access the data within, do so by const reference and const_iterator unless you need to modify any of it. This will ensure that

  1. You don't have duplication of data
  2. You're making maximum use of the STL, thereby minimizing your own code and the possible bugs
  3. You're relying on algorithms and containers that are already really efficient
  4. You're using the STL in a way it was meant to be used.

Hope this helps.

One possible problem could be in copies of record instances.

struct record 
{
    vector<string> dataRow;
    vector<string *> keys;
    vector<string *> values;
};

In fact, default copy constructor and copy operator= do a member-wise copy . This is OK for dataRow field (which is a vector<string> ), but this is bad for keys and values fields (since these are vectors of raw pointers , their values are copied, but they point to something wrong).

I'd reconsider your design, eg using vector<int> instead of vector<string *> for keys and values fields. The int s stored would be indexes in the dataRow vector.

Another note (not directly related to your problem). In C++11, when you want to copy something, you may want to pass by value, and move from the value:

void setDataRow(vector<string> r) 
{
     dataRow = std::move(r);
}

Or just use old C++98/03 style of passing by const ref:

void setDataRow(const vector<string>& r) 
{
     dataRow = r;
}

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