Последовательный двоичный файл

При тестировании списка объектов Product программа просто завершает работу с кодом -1073741819(при выборе любой функции). Помогите найти ошибку.

шаблон для работы с двоичным файлом

#pragma once
#include <string>
#include <iostream>
#include <fstream>
#include <stdexcept>
#include <vector>
#include <algorithm>
#include <functional>

#define MAXitemsPerPage 30

template <typename T>
class Node {
public:
    T data;
    long next; // Указатель на следующий узел
    long prev; // Указатель на предыдущий узел

    // Конструктор с параметрами
    Node(const T& data) : data(data), next(-1), prev(-1) {}

    // Конструктор по умолчанию
    Node() : data(T()), next(-1), prev(-1) {} // Инициализация data значением по умолчанию
};


template <typename T>
class BaseList {
public:
    BaseList(const std::string& filename);
    ~BaseList();

    void add(const T& item); // Добавление элемента
    void remove(size_t index); // Удаление элемента по индексу
    T get(size_t index); // Извлечение элемента по индексу
    void insert(size_t index, const T& item); // Вставка элемента по логическому номеру
    void update(size_t index, const T& item); // Обновление элемента
    size_t getSize() const; // Получение размера списка
    void display(); // Отображение списка
    void displayPage(size_t pageNumber); // Постраничный просмотр
    void sort(const std::function<bool(const T&, const T&)>& comparator);
    void compressFile(const std::string& outputFile); // Сжатие файла
    void loadFromFile(); // Загрузка из файла
    void saveToFile() const; // Сохранение в файл

private:
    long head; // Указатель на голову списка
    long tail; // Указатель на конец списка
    size_t size; // Размер списка
    std::fstream file; // Файл для хранения данных
    std::string filename; // Имя файла
};

template <typename T>
BaseList<T>::BaseList(const std::string& filename) : filename(filename), size(0), head(-1), tail(-1) {
    file.open(filename, std::ios::binary | std::ios::in | std::ios::out);
    if (!file.is_open()) {
        file.open(filename, std::ios::binary | std::ios::out);
        file.close();
        file.open(filename, std::ios::binary | std::ios::in | std::ios::out);
    }
    loadFromFile();
}

template <typename T>
BaseList<T>::~BaseList() {
    saveToFile();
    file.close();
}

template <typename T>
void BaseList<T>::add(const T& item) {
    Node<T> newNode(item);
    long newNodePos = file.tellp(); // Позиция нового узла

    file.seekp(0, std::ios::end);
    file.write(reinterpret_cast<const char*>(&newNode), sizeof(Node<T>));
    size++;

    if (head == -1) {
        head = newNodePos; // Если список пуст, новый узел - голова
        tail = newNodePos; // И конец
    }
    else {
        // Обновляем указатели
        file.seekp(tail);
        Node<T> tailNode;
        file.read(reinterpret_cast<char*>(&tailNode), sizeof(Node<T>));
        tailNode.next = newNodePos;
        file.seekp(tail);
        file.write(reinterpret_cast<const char*>(&tailNode), sizeof(Node<T>));
        tail = newNodePos;
    }
}

template <typename T>
void BaseList<T>::remove(size_t index) {
    if (index >= size) throw std::out_of_range("Index out of range");

    long currentPos = head;
    long prevPos = -1;

    for (size_t i = 0; i < index; ++i) {
        prevPos = currentPos;
        file.seekg(currentPos);
        Node<T> currentNode;
        file.read(reinterpret_cast<char*>(&currentNode), sizeof(Node<T>));
        currentPos = currentNode.next;
    }

    // Удаляем узел
    Node<T> currentNode;
    file.seekg(currentPos);
    file.read(reinterpret_cast<char*>(&currentNode), sizeof(Node<T>));

    if (prevPos != -1) {
        // Если не удаляем голову
        file.seekp(prevPos);
        Node<T> prevNode;
        file.read(reinterpret_cast<char*>(&prevNode), sizeof(Node<T>));
        prevNode.next = currentNode.next;
        file.seekp(prevPos);
        file.write(reinterpret_cast<const char*>(&prevNode), sizeof(Node<T>));
    }
    else {
        // Если удаляем голову
        head = currentNode.next;
    }

    if (currentNode.next != -1) {
        // Если не удаляем последний элемент
        file.seekp(currentNode.next);
        Node<T> nextNode;
        file.read(reinterpret_cast<char*>(&nextNode), sizeof(Node<T>));
        nextNode.prev = prevPos;
        file.seekp(currentNode.next);
        file.write(reinterpret_cast<const char*>(&nextNode), sizeof(Node<T>));
    }
    else {
        // Если удаляем последний элемент
        tail = prevPos;
    }

    size--;
}

template <typename T>
T BaseList<T>::get(size_t index) {
    if (index >= size) throw std::out_of_range("Index out of range");

    long currentPos = head;

    for (size_t i = 0; i < index; ++i) {
        file.seekg(currentPos);
        Node<T> currentNode;
        file.read(reinterpret_cast<char*>(&currentNode), sizeof(Node<T>));
        currentPos = currentNode.next;
    }

    file.seekg(currentPos);
    Node<T> currentNode;
    file.read(reinterpret_cast<char*>(&currentNode), sizeof(Node<T>));
    return currentNode.data;
}

template <typename T>
void BaseList<T>::insert(size_t index, const T& item) {
    if (index > size) throw std::out_of_range("Index out of range");

    Node<T> newNode(item);
    long newNodePos = file.tellp(); // Позиция нового узла

    file.seekp(0, std::ios::end);
    file.write(reinterpret_cast<const char*>(&newNode), sizeof(Node<T>));
    size++;

    if (index == 0) {
        // Вставка в начало
        newNode.next = head;
        if (head != -1) {
            file.seekp(head);
            Node<T> headNode;
            file.read(reinterpret_cast<char*>(&headNode), sizeof(Node<T>));
            headNode.prev = newNodePos;
            file.seekp(head);
            file.write(reinterpret_cast<const char*>(&headNode), sizeof(Node<T>));
        }
        head = newNodePos;
        newNode.prev = -1;
    }
    else {
        // Вставка в середину или конец
        long currentPos = head;
        long prevPos = -1;

        for (size_t i = 0; i < index; ++i) {
            prevPos = currentPos;
            file.seekg(currentPos);
            Node<T> currentNode;
            file.read(reinterpret_cast<char*>(&currentNode), sizeof(Node<T>));
            currentPos = currentNode.next;
        }

        newNode.next = currentPos;
        newNode.prev = prevPos;

        if (prevPos != -1) {
            file.seekp(prevPos);
            Node<T> prevNode;
            file.read(reinterpret_cast<char*>(&prevNode), sizeof(Node<T>));
            prevNode.next = newNodePos;
            file.seekp(prevPos);
            file.write(reinterpret_cast<const char*>(&prevNode), sizeof(Node<T>));
        }

        if (currentPos != -1) {
            file.seekp(currentPos);
            Node<T> currentNode;
            file.read(reinterpret_cast<char*>(&currentNode), sizeof(Node<T>));
            currentNode.prev = newNodePos;
            file.seekp(currentPos);
            file.write(reinterpret_cast<const char*>(&currentNode), sizeof(Node<T>));
        }
    }

    if (newNode.next == -1) {
        tail = newNodePos; // Обновить tail, если вставка в конец
    }
}

template <typename T>
void BaseList<T>::sort(const std::function<bool(const T&, const T&)>& comparator) {
    std::vector<T> items;
    long currentPos = head;

    // Извлечение всех элементов в вектор
    while (currentPos != -1) {
        file.seekg(currentPos);
        Node<T> currentNode;
        file.read(reinterpret_cast<char*>(&currentNode), sizeof(Node<T>));
        items.push_back(currentNode.data);
        currentPos = currentNode.next;
    }

    // Сортировка вектора с использованием компаратора
    std::sort(items.begin(), items.end(), comparator);

    // Очистка текущего списка
    head = -1;
    tail = -1;
    size = 0;

    // Вставка отсортированных элементов обратно в список
    for (const auto& item : items) {
        add(item);
    }
}


template <typename T>
void BaseList<T>::update(size_t index, const T& item) {
    if (index >= size) throw std::out_of_range("Index out of range");

    long currentPos = head;

    for (size_t i = 0; i < index; ++i) {
        file.seekg(currentPos);
        Node<T> currentNode;
        file.read(reinterpret_cast<char*>(&currentNode), sizeof(Node<T>));
        currentPos = currentNode.next;
    }

    file.seekp(currentPos);
    Node<T> currentNode;
    file.read(reinterpret_cast<char*>(&currentNode), sizeof(Node<T>));
    currentNode.data = item;
    file.seekp(currentPos);
    file.write(reinterpret_cast<const char*>(&currentNode), sizeof(Node<T>));
}

template <typename T>
size_t BaseList<T>::getSize() const {
    return size;
}

template <typename T>
void BaseList<T>::display() {
    long currentPos = head;
    while (currentPos != -1) {
        file.seekg(currentPos);
        Node<T> currentNode;
        file.read(reinterpret_cast<char*>(&currentNode), sizeof(Node<T>));
        std::cout << currentNode.data << " ";
        currentPos = currentNode.next;
    }
    std::cout << std::endl;
}

template <typename T>
void BaseList<T>::displayPage(size_t pageNumber) {
    long currentPos = head;
    size_t startIndex = (pageNumber - 1) * MAXitemsPerPage;

    for (size_t i = 0; i < startIndex && currentPos != -1; ++i) {
        file.seekg(currentPos);
        Node<T> currentNode;
        file.read(reinterpret_cast<char*>(&currentNode), sizeof(Node<T>));
        currentPos = currentNode.next;
    }

    for (size_t i = 0; i < MAXitemsPerPage && currentPos != -1; ++i) {
        file.seekg(currentPos);
        Node<T> currentNode;
        file.read(reinterpret_cast<char*>(&currentNode), sizeof(Node<T>));
        std::cout << currentNode.data << " ";
        currentPos = currentNode.next;
    }
    std::cout << std::endl;
}

template <typename T>
void BaseList<T>::compressFile(const std::string& outputFile) {
    std::vector<T> uniqueItems;
    long currentPos = head;

    while (currentPos != -1) {
        file.seekg(currentPos);
        Node<T> currentNode;
        file.read(reinterpret_cast<char*>(&currentNode), sizeof(Node<T>));
        if (std::find(uniqueItems.begin(), uniqueItems.end(), currentNode.data) == uniqueItems.end()) {
            uniqueItems.push_back(currentNode.data);
        }
        currentPos = currentNode.next;
    }

    std::ofstream outFile(outputFile, std::ios::binary);
    for (const auto& uniqueItem : uniqueItems) {
        outFile.write(reinterpret_cast<const char*>(&uniqueItem), sizeof(T));
    }
    outFile.close();
}

template <typename T>
void BaseList<T>::loadFromFile() {
    file.seekg(0, std::ios::end);
    size = file.tellg() / sizeof(Node<T>);
    file.seekg(0, std::ios::beg);
}

template <typename T>
void BaseList<T>::saveToFile() const {
    // Сохранение уже происходит в деструкторе
}

класс который работает с Product

#pragma once
#include "ClassProduct.h"
#include "BaseList.h"
#include <string>
#include <iostream>
#include <fstream>
#include <stdexcept>
#include <vector>

using namespace std;

class ProductList : public BaseList<Product> {
public:
    ProductList(const std::string& filename) : BaseList<Product>(filename) {}

    void sortByName();
    void sortByCategory();
    void sortByQuantity();
    void sortByArrivalDate();
    void sortByPrice();
    void sortByMarkup();
    void generateInvoice(); // Генерация счета 
        
};

класс Product

#pragma once
#include <string>
#include <iostream>
#include <fstream>
#include <stdexcept>

using namespace std;

class Product {
public:
    Product();
    Product(const string& _name, const string& _category, int _quantity,
        const string& _arrivalDate, double _price, double _markupPercentage);
    Product(const Product& obj);
    ~Product();

    // Геттеры
    string getName() const;
    string getCategory() const;
    int getQuantity() const;
    string getArrivalDate() const;
    double getPrice() const;
    double getMarkupPercentage() const;

    // Сеттеры
    void setName(const string& _name);
    void setCategory(const string& _category);
    void setQuantity(int _quantity);
    void setArrivalDate(const string& _arrivalDate);
    void setPrice(double _price);
    void setMarkupPercentage(double _markupPercentage);

    // Сериализация
    void serialize(std::ostream& os) const;
    void deserialize(std::istream& is);

    // Операторы ввода-вывода
    friend ostream& operator<<(ostream& os, const Product& product);
    friend istream& operator>>(istream& is, Product& product);

    bool operator==(const Product& other) const;

    

private:
    string name;
    string category;
    int quantity;
    string arrivalDate;
    double price;
    double markupPercentage;
};

функция теста

void testProductList() {
    ProductList productList("testProductList2.bin");
    Product product;
    int choice;

    while (true) {
        std::cout << "Меню для тестирования списка продуктов:\n";
        std::cout << "1. Добавить продукт\n";
        std::cout << "2. Удалить продукт\n";
        std::cout << "3. Обновить продукт\n";
        std::cout << "4. Отобразить продукты\n";
        std::cout << "5. Сортировать по имени\n";
        std::cout << "6. Сортировать по цене\n";
        std::cout << "7. Генерация счета\n";
        std::cout << "8. Постраничный просмотр\n";
        std::cout << "9. Вставить продукт\n";
        std::cout << "10. Сжать файл\n";
        std::cout << "11. Выход\n";
        std::cout << "Выберите операцию: ";
        std::cin >> choice;

        switch (choice) {
        case 1:
            std::cout << "Введите данные продукта (имя, категория, количество, дата поступления, цена, наценка):\n";
            std::cin >> product;
            productList.add(product);
            break;
        case 2:
            std::cout << "Введите индекс продукта для удаления: ";
            std::cin >> choice;
            try {
                productList.remove(choice);
            }
            catch (const std::out_of_range& e) {
                std::cout << e.what() << std::endl;
            }
            break;
        case 3:
            std::cout << "Введите индекс продукта для обновления: ";
            std::cin >> choice;
            if (choice < 0 || choice >= productList.getSize()) {
                std::cout << "Индекс вне диапазона." << std::endl;
                break;
            }
            std::cout << "Введите новые данные продукта (имя, категория, количество, дата поступления, цена, наценка):\n";
            std::cin >> product; // Предполагается перегрузка оператора >>
            try {
                productList.update(choice, product); // Обновляем с новыми данными
            }
            catch (const std::out_of_range& e) {
                std::cout << e.what() << std::endl;
            }
            break;
        case 4:
            productList.display();
            break;
        case 5:
            productList.sortByName(); // Предполагается, что метод sortByName() существует
            std::cout << "Список отсортирован по имени.\n";
            break;
        case 6:
            productList.sortByPrice(); // Предполагается, что метод sortByPrice() существует
            std::cout << "Список отсортирован по цене.\n";
            break;
        case 7:
            productList.generateInvoice(); // Предполагается, что метод generateInvoice() существует
            break;
        case 8: {
            size_t pageNumber;
            std::cout << "Введите номер страницы: ";
            std::cin >> pageNumber;
            productList.displayPage(pageNumber);
            break;
        }
        case 9: {
            std::cout << "Введите индекс для вставки: ";
            size_t index;
            std::cin >> index;
            std::cout << "Введите данные продукта (имя, категория, количество, дата поступления, цена, наценка):\n";
            std::cin >> product;
            try {
                productList.insert(index, product);
            }
            catch (const std::out_of_range& e) {
                std::cout << e.what() << std::endl;
            }
            break;
        }
        case 10: {
            std::string outputFile;
            std::cout << "Введите имя выходного файла: ";
            std::cin >> outputFile;
            productList.compressFile(outputFile);
            std::cout << "Файл сжат в " << outputFile << std::endl;
            break;
        }
        case 11:
            return;
        default:
            std::cout << "Неверный выбор. Попробуйте снова.\n";
        }
    }
}

Выложите еще и код реализации классов. По текущему коду: нужно в BaseList<T>::loadFromFile брать откуда-то head и tail (или сохранять их в файл, или вычитывать весь список и восстанавливать их); если список двунаправленный, то нужно в BaseList<T>::add для нового элемента списка задавать значение поля prev, а не только для предыдущего элемента списка выставлять next. Остальные методы не смотрел.