Странное поведение с полями классов при добавлении в std :: vector - PullRequest
31 голосов
/ 02 марта 2020

Я обнаружил очень странное поведение (на Clang и G CC) в следующей ситуации. У меня есть вектор, nodes, с одним элементом, экземпляр класса Node. Затем я вызываю функцию на nodes[0], которая добавляет новый Node к вектору. Когда новый узел добавлен, поля вызывающего объекта сбрасываются! Тем не менее, они, похоже, снова возвращаются к нормальному состоянию после завершения функции.

Я считаю, что это минимальный воспроизводимый пример:

#include <iostream>
#include <vector>

using namespace std;

struct Node;
vector<Node> nodes;

struct Node{
    int X;
    void set(){
        X = 3;
        cout << "Before, X = " << X << endl;
        nodes.push_back(Node());
        cout << "After, X = " << X << endl;
    }
};

int main() {
    nodes = vector<Node>();
    nodes.push_back(Node());

    nodes[0].set();
    cout << "Finally, X = " << nodes[0].X << endl;
}

Какие выходные данные

Before, X = 3
After, X = 0
Finally, X = 3

Хотя вы ожидаете, что X останется неизменным в процессе.

Другие вещи, которые я пробовал:

  • Если я уберу строку, которая добавляет Node внутри set(), то каждый раз он выдает X = 3.
  • Если я создаю новый Node и вызываю его на этом (Node p = nodes[0]), то получается 3, 3, 3
  • Если я создаю ссылка Node и вызовите ее на этом (Node &p = nodes[0]), тогда на выходе будет 3, 0, 0 (возможно, это потому, что ссылка теряется при изменении размера вектора?)

это неопределенное поведение по какой-то причине? Почему?

Ответы [ 2 ]

39 голосов
/ 02 марта 2020

Ваш код имеет неопределенное поведение. В

void set(){
    X = 3;
    cout << "Before, X = " << X << endl;
    nodes.push_back(Node());
    cout << "After, X = " << X << endl;
}

Доступ к X действительно this->X, а this - указатель на член вектора. Когда вы делаете nodes.push_back(Node());, вы добавляете новый элемент в вектор, и этот процесс перераспределяет, что делает недействительными все итераторы, указатели и ссылки на элементы в векторе. Это означает, что

cout << "After, X = " << X << endl;

использует this, который больше не действителен.

15 голосов
/ 02 марта 2020
nodes.push_back(Node());

перераспределяет вектор, изменяя адрес nodes[0], но this не обновляется.
попробуйте заменить метод set на этот код:

    void set(){
        X = 3;
        cout << "Before, X = " << X << endl;
        cout << "Before, this = " << this << endl;
        cout << "Before, &nodes[0] = " << &nodes[0] << endl;
        nodes.push_back(Node());
        cout << "After, X = " << X << endl;
        cout << "After, this = " << this << endl;
        cout << "After, &nodes[0] = " << &nodes[0] << endl;
    }

обратите внимание, что &nodes[0] отличается после вызова push_back.

-fsanitize=address поймает это и даже скажет вам, на какой строке была освобождена память, если вы также скомпилируете с -g.

...