Контейнер с поддержкой транзакций
Необходимо написать контейнер (на базе vector или deque), который бы поддерживал операции begin_transaction(), rollback_transaction() и commit_transaction().
При выполнении операций вставки/модификации/удаления данных из контейнера , эти операции должны оборачиваться в транзакцию, для возможности применения или отката изменений.
Активная транзакция для контейнера может существовать только одна (для упрощения жизни).
Как я это вижу:
Class TrVector
{
void begin_transaction();
void rollback_transaction();
void commit_transaction();
...
void insert(int item);
void update(int item);
void delete(int item);
...
std::atomic_bool m_transaction;
std::vector<int> m_data;
std::vector<int> m_tr_insert_data;
std::vector<int> m_tr_update_data;
std::vector<int> m_tr_delete_data;
}
void TrVector::begin_transaction()
{
if (m_transaction == true)
{
rollback_transaction();
}
m_transaction = true;
}
void TrVector::rollback_transaction()
{
m_tr_insert_data.clear();
m_tr_update_data.clear();
m_tr_delete_data.clear();
m_transaction = false;
}
void TrVector::commit_transaction()
{
m_data.delete(m_tr_insert_data.begin(), m_tr_insert_data.end()); // тут будет более сложная логика, т.к. в векторе нету никаких delete/update)
m_data.update(m_tr_insert_data.begin(), m_tr_insert_data.end());
m_data.insert(m_tr_insert_data.begin(), m_tr_insert_data.end());
m_tr_insert_data.clear();
m_tr_update_data.clear();
m_tr_delete_data.clear();
m_transaction = false;
}
void TrVector::insert(int item)
{
if (m_transaction == true)
{
m_data.insert(item);
}
else
{
m_tr_insert_data.insert(item);
}
}
Я собственно только в начале пути, поэтому интересует следующее: правильный ли такой подход, возможно это уже было реализовано в бусте/кьюте, буду благодарен за литературу по теме или идеи как это еще можно реализовать.
Upd: кейс использования
try
{
container.begin_transaction();
// что-то делаем с контейнером, вставляем, удаляем...
}
catch (const std::exception& e)
{
container.rollback_transaction();
}
Ответы (2 шт):
В целом, все просто. Если памяти не жалко, то при начале транзакции нужно просто сделать копию контейнера. Все вставки дальше в нее. Если транзакцию нужно откатить - просто выкидываем копию. Если транзакцию нужно закоммитить - копируем копию в основной контейнер.
Если же память жалко, то можно сделать на базе "лога". То есть, каждая операция, которая модифицирует контейнер, запоминает предыдущее состояние или умеет сделать откат. То есть, обычный insert просто помнит какой элемент он вставил, что бы потом удалить. delete должен запомнить удаляемый элемент и потом восстановить по надобности.
Набросал очень грязный код, но как пример подойдет. Как основу - использует вектор, но переделать на что то другое думаю не будет проблем. Не забудьте реализовать все свои функции, также конструкторы/операторы копирования/перемещения (а такой контейнер наверно можно копировать только когда транзакция не началась?). Я не сильно уверен, что я правильно сделал итераторы, но вроде оно работает как нужно.
#include <iostream>
#include <fstream>
#include <vector>
#include <cassert>
template <typename T>
class TransactionVector
{
private:
std::vector<T> m_old;
std::vector<T> m_current;
bool m_transaction = false;
public:
void begin_transaction() { assert(!m_transaction); m_old = m_current; m_transaction = true;}
void commit_transaction() { assert(m_transaction);m_old = {};m_transaction = false;}
void rollback_transaction() {assert(m_transaction);m_current = m_old; m_old = {};m_transaction = false;}
TransactionVector() : m_old(), m_current() {}
TransactionVector(std::initializer_list<T> init) : m_old(), m_current(init) {
}
// your functions
void push_back(const T& value) {
m_current.push_back(value);
}
bool empty() const {
return m_current.empty();
}
typename std::vector<T>::iterator begin() { return m_current.begin();}
typename std::vector<T>::iterator end() { return m_current.end();}
typename std::vector<T>::const_iterator cbegin() const { return m_current.cbegin();}
typename std::vector<T>::const_iterator cend() const { return m_current.cend();}
};
template <typename T>
std::ostream& operator<<(std::ostream& os, const TransactionVector<T>& v)
{
os << '[';
if (!v.empty()) {
os << *v.cbegin();
for (auto it = v.cbegin()+1; it != v.cend(); it++) {
os << ", " << *it;
}
}
os << ']';
return os;
}
int main()
{
TransactionVector<int> a = {1,2,3};
std::cout << a << '\n';
a.begin_transaction();
a.push_back(1);
//a.commit_transaction();
a.rollback_transaction();
std::cout << a << '\n';
}
Возможный вариант: для каждой выполненной операции генерируется противоположная операция, и записыватся в вектор. Это должен быть вектор функций. Напрашивается применение lambda. Если что-то не в порядке - выполняем все противоположные функции в обратном порядке.