mirror of
https://github.com/subsurface/subsurface.git
synced 2025-02-19 22:16:15 +00:00
core: replace divesite_table_t by a vector of std::unique_ptr<>s
This is a long commit, because it introduces a new abstraction: a general std::vector<> of std::unique_ptrs<>. Moreover, it replaces a number of pointers by C++ references, when the callee does not suppoert null objects. This simplifies memory management and makes ownership more explicit. It is a proof-of-concept and a test-bed for the other core data structrures. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
parent
411188728d
commit
e39dea3d68
41 changed files with 451 additions and 426 deletions
153
core/owning_table.h
Normal file
153
core/owning_table.h
Normal file
|
@ -0,0 +1,153 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Traditionally, most of our data structures are collected as tables
|
||||
* (=arrays) of pointers. The advantage is that pointers (or references)
|
||||
* to objects are stable, even if the array grows and reallocates.
|
||||
* Implicitly, the table is the owner of the objects and the objets
|
||||
* are deleted (freed) when removed from the table.
|
||||
* This header defines an std::vector<> of unique_ptr<>s to make this
|
||||
* explicit.
|
||||
*
|
||||
* The state I want to reach across the code base: whenever a part of the
|
||||
* code owns a heap allocated object, it *always* possesses a unique_ptr<>
|
||||
* to that object. All naked pointers are invariably considered as
|
||||
* "non owning".
|
||||
*
|
||||
* There are two ways to end ownership:
|
||||
* 1) The std::unique_ptr<> goes out of scope and the object is
|
||||
* automatically deleted.
|
||||
* 2) Ownership is passed to another std::unique_ptr<> using std::move().
|
||||
*
|
||||
* This means that when adding an object to an owning_table,
|
||||
* ownership of a std::unique_ptr<> is given up with std::move().
|
||||
* The table then returns a non-owning pointer to the object and
|
||||
* optionally the insertion index.
|
||||
*
|
||||
* In converse, when removing an object, one provides a non-owning
|
||||
* pointer, which is turned into an owning std::unique_ptr<> and
|
||||
* the index where the object was removed.
|
||||
* When ignoring the returned owning pointer, the object is
|
||||
* automatically freed.
|
||||
*
|
||||
* The functions to add an entry to the table are called "put()",
|
||||
* potentially with a suffix. The functions to remove an entry
|
||||
* are called "pull()", likewise with an optional suffix.
|
||||
*
|
||||
* There are two versions of the table:
|
||||
* 1) An unordered version, where the caller is responsible for
|
||||
* adding at specified positions (either given by an index or at the end).
|
||||
* Removal via a non-owning pointer is implemented by a linear search
|
||||
* over the whole table.
|
||||
* 2) An ordered version, where a comparison function that returns -1, 0, 1
|
||||
* is used to add objects. In that case, the caller must make sure that
|
||||
* no two ojects that compare equal are added to the table.
|
||||
* Obviously, the compare function is supposed to be replaced by the
|
||||
* "spaceship operator" in due course.
|
||||
* Here, adding and removing via non-owning pointers is implemented
|
||||
* using a binary search.
|
||||
*
|
||||
* Note that, since the table contains std::unique_ptr<>s, to loop over
|
||||
* all entries, it is best to use something such as
|
||||
* for (const auto &ptr: table) ...
|
||||
* I plan to add iterator adapters, that autometically dereference
|
||||
* the unique_ptr<>s and provide const-references for const-tables.
|
||||
*
|
||||
* Time will tell how useful this class is.
|
||||
*/
|
||||
#ifndef CORE_OWNING_TABLE_H
|
||||
#define CORE_OWNING_TABLE_H
|
||||
|
||||
#include "errorhelper.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
template <typename T>
|
||||
class owning_table : public std::vector<std::unique_ptr<T>> {
|
||||
public:
|
||||
struct put_result {
|
||||
T *ptr;
|
||||
size_t idx;
|
||||
};
|
||||
struct pull_result {
|
||||
std::unique_ptr<T> ptr;
|
||||
size_t idx;
|
||||
};
|
||||
size_t get_idx(const T *item) const {
|
||||
auto it = std::find_if(this->begin(), this->end(),
|
||||
[item] (auto &i1) {return i1.get() == item; });
|
||||
return it != this->end() ? it - this->begin() : std::string::npos;
|
||||
}
|
||||
T *put_at(std::unique_ptr<T> item, size_t idx) {
|
||||
T *res = item.get();
|
||||
insert(this->begin() + idx, std::move(item));
|
||||
return res;
|
||||
}
|
||||
// Returns index of added item
|
||||
put_result put_back(std::unique_ptr<T> item) {
|
||||
T *ptr = item.get();
|
||||
push_back(std::move(item));
|
||||
return { ptr, this->size() - 1 };
|
||||
}
|
||||
std::unique_ptr<T> pull_at(size_t idx) {
|
||||
auto it = this->begin() + idx;
|
||||
std::unique_ptr<T> res = std::move(*it);
|
||||
this->erase(it);
|
||||
return res;
|
||||
}
|
||||
pull_result pull(const T *item) {
|
||||
size_t idx = get_idx(item);
|
||||
if (idx == std::string::npos) {
|
||||
report_info("Warning: removing unexisting item in %s", __func__);
|
||||
return { std::unique_ptr<T>(), std::string::npos };
|
||||
}
|
||||
return { pull_at(idx), idx };
|
||||
}
|
||||
};
|
||||
|
||||
// Note: there must not be any elements that compare equal!
|
||||
template <typename T, int (*CMP)(const T &, const T &)>
|
||||
class sorted_owning_table : public owning_table<T> {
|
||||
public:
|
||||
using typename owning_table<T>::put_result;
|
||||
using typename owning_table<T>::pull_result;
|
||||
// Returns index of added item
|
||||
put_result put(std::unique_ptr<T> item) {
|
||||
auto it = std::lower_bound(this->begin(), this->end(), item,
|
||||
[] (const auto &i1, const auto &i2)
|
||||
{ return CMP(*i1, *i2) < 0; });
|
||||
if (it != this->end() && CMP(**it, *item) == 0)
|
||||
report_info("Warning: adding duplicate item in %s", __func__);
|
||||
size_t idx = it - this->begin();
|
||||
T *ptr = item.get();
|
||||
this->insert(it, std::move(item));
|
||||
return { ptr, idx };
|
||||
}
|
||||
// Optimized version of get_idx(), which uses binary search
|
||||
// If not found, fall back to linear search and emit a warning.
|
||||
// Note: this is probaly slower than a linesr search. But for now,
|
||||
// it helps finding consistency problems.
|
||||
size_t get_idx(const T *item) const {
|
||||
auto it = std::lower_bound(this->begin(), this->end(), item,
|
||||
[] (const auto &i1, const auto &i2)
|
||||
{ return CMP(*i1, *i2) < 0; });
|
||||
if (it == this->end() || CMP(**it, *item) != 0) {
|
||||
size_t idx = owning_table<T>::get_idx(item);
|
||||
if (idx != std::string::npos)
|
||||
report_info("Warning: index found by linear but not by binary search in %s", __func__);
|
||||
return idx;
|
||||
}
|
||||
return it - this->begin();
|
||||
}
|
||||
pull_result pull(const T *item) {
|
||||
size_t idx = get_idx(item);
|
||||
if (idx == std::string::npos) {
|
||||
report_info("Warning: removing unexisting item in %s", __func__);
|
||||
return { std::unique_ptr<T>(), std::string::npos };
|
||||
}
|
||||
return { this->pull_at(idx), idx };
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue