Потерять ссылку на объект std :: option после изменения его типа - PullRequest
2 голосов
/ 26 января 2020

Обратите внимание, что проблему можно воспроизвести, запустив приведенный ниже фрагмент кода (я использую wandbox с g cc 9.1)

Так что у меня есть std::array (размер 2 для простоты) std::variant из два пользовательских типа (Normal и Special) Normal определены в качестве первого типа, поэтому при построении класса массив создается по умолчанию с Normal объектами. Я изменяю некоторые внутренние элементы данных первого элемента массива и распечатываю его. Выглядит хорошо.

Теперь я хочу установить второй элемент массива на Special объект. Я попытался сделать это, присвоив новое значение и используя emplace в соответствии с этим руководством (https://www.bfilipek.com/2018/06/variant.html#changing -the-значения )

Однако, когда я пытаюсь изменить внутреннее члены данных второго объекта (теперь набираются Special), похоже, что я не оперирую объектом в исходных массивах. Результаты распечатки показывают значение конструкции по умолчанию (в данном случае 0). Я новичок в использовании std::variant, поэтому не имею понятия, почему это так. Как я могу получить фактическую ссылку на недавно измененный тип объекта в моем массиве?

#include <iostream>
#include <memory>
#include <cstring>
#include <array>
#include <variant>

struct Normal {
    struct Header {
        std::array<uint8_t, 2> reserved;
    };
    Normal() : frame{0}, payload{reinterpret_cast<uint8_t*>(frame + sizeof(Header))} {}
    constexpr static auto LENGTH = 10;
    uint8_t frame[LENGTH];
    uint8_t* payload;
};

struct Special {
    struct Header {
        std::array<uint8_t, 3> reserved;
    };
    Special() : frame{0}, payload{reinterpret_cast<uint8_t*>(frame + sizeof(Header))} {}
    constexpr static auto LENGTH = 11;
    uint8_t frame[LENGTH];
    uint8_t* payload;
};

std::array<std::variant<Normal, Special>, 2> handlers;
Normal* normal_handler;
Special* special_handler;

int main() {
    auto& nh = std::get<Normal>(handlers[0]);
    memset(nh.payload, 3, 3);
    normal_handler = &nh;

    handlers[1].emplace<1>(Special{});
    auto& sh = std::get<Special>(handlers[1]);
    memset(sh.payload, 4 ,4);
    // memset(std::get<Special>(handlers[1]).payload, 4, 4);
    special_handler = &sh;

    for (int i = 0; i < 10; i++) {
        // Expect 3 bytes from 3rd bytes = 3
        std::cout << (int) normal_handler->frame[i] << " ";
    }

    std::cout << std::endl;

    for (int i = 0; i < 11; i++) {
        // Expect 4 bytes from 4th bytes = 4
        std::cout << (int) special_handler->frame[i] << " ";
        // std::cout << (int) std::get<Special>(handlers[1]).frame[i] << " ";
    }

}

1 Ответ

3 голосов
/ 26 января 2020

Ваша проблема не связана с std::variant, следующий код демонстрирует такое же поведение:

#include <iostream>
#include <memory>
#include <cstring>

struct Special {
    struct Header {
        std::array<uint8_t, 3> reserved;
    };
    Special() : frame{0}, payload{reinterpret_cast<uint8_t*>(frame + sizeof(Header))} {}
    constexpr static auto LENGTH = 11;
    uint8_t frame[LENGTH];
    uint8_t* payload;
};

int main() {

    Special s1;
    s1 = Special{};
    memset(s1.payload, 4 ,4);

    for (int i = 0; i < 11; i++) {
        // Expect 4 bytes from 4th bytes = 4
        std::cout << (int) s1.frame[i] << " ";
    }
}

Эта строка:

    s1 = Special{};

Создает временный объект Special а затем присваивает его s1. Конструкторы копирования и перемещения по умолчанию установят s1.payload во временное значение payload. Следовательно, s1.payload является висящим указателем на frame во временном объекте, и, следовательно, остальная часть вашего кода имеет неопределенное поведение.

Самое простое решение - изменить payload член в функцию:

#include <iostream>
#include <memory>
#include <cstring>

struct Special {
    struct Header {
        std::array<uint8_t, 3> reserved;
    };
    Special() : frame{0} {}
    constexpr static auto LENGTH = 11;
    uint8_t frame[LENGTH];
    uint8_t* payload() { return &frame[sizeof(Header)]; }
};

int main() {

    Special s1;
    s1 = Special{};
    memset(s1.payload(), 4 ,4);

    for (int i = 0; i < 11; i++) {
        // Expect 4 bytes from 4th bytes = 4
        std::cout << (int) s1.frame[i] << " ";
    }

}
...