Со своим коллегой я нашел два решения, которые решают мою проблему.
Решение 1 - Хакерское
Идея состоит в том, чтобы использовать структуру базовой реализации std::vector<double>
, который состоит из в моем случае из 3 элементов, содержащих 3 указателя на данные вектора.
- начальный адрес раздела данных
- конечный адрес раздела данных
- адрес текущей максимальной емкости раздела данных
Итак, я создаю struct
, содержащий эти три адреса, и использую reinterpret_cast
до std::vector
. Это работает с текущей реализацией std::vector
на моей машине. Эта реализация может отличаться в зависимости от установленной версии STL.
Приятно то, что я могу использовать интерфейс std::vector
, не создавая его. Мне также не нужно копировать данные в std::vector
. Я также мог взять часть исходных данных, хранящихся в моем сложном классе. Я могу управлять управляемой частью с помощью указателей, которые я отправляю в структуру.
Это решает мою проблему , но это взлом. Могу использовать, потому что код актуален только для меня. До сих пор выкладываю, потому что это может быть интересно другим.
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
//--------------------------------------------------------------------------//
//--- This function is given, I cannot change its signature.
bool
func_in_lib(std::vector<double>& data, double& res) {
//--- check some properties of the vector
if (data.size() < 10)
return false;
//--- do something magical with the data
for (auto& d : data)
d *= 2.0;
res = 42.0;
return true;
}
//--------------------------------------------------------------------------//
struct DataType {
double a = 1.0;
double b = 2.0;
double c = 3.0;
};
//--------------------------------------------------------------------------//
ostream&
operator<<(ostream& out, const DataType& d) {
out << d.a << " " << d.b << " " << d.c << endl;
return out;
}
//--------------------------------------------------------------------------//
int
main(int argc, char const* argv[]) {
int count = 20;
//--- init and print my data
unique_ptr<DataType[]> my_data = make_unique<DataType[]>(count);
for (int i = 0; i < count; ++i)
cout << my_data.get()[i];
//--------------------------------------------------------------------------//
// HERE STARTS THE UGLY HACK, THAT CAN BE ERROR-PRONE BECAUSE IT DEPENDS ON
// THE UNDERLYING IMPLEMENTATION OF std::vector<T>
//--------------------------------------------------------------------------//
struct VecAccess {
double* start = nullptr; // address to the start of the data
double* stop0 = nullptr; // address to the end of the data
double* stop1 = nullptr; // address to the capacity of the vector
};
//---
DataType* p_data = my_data.get();
VecAccess va{ &(p_data[0].a), //points at the 'front' of the vector
&(p_data[count - 1].c) + 1, //points at the 'end' of the vector
&(p_data[count - 1].c) + 1 };
vector<double>* p_vec_access = reinterpret_cast<vector<double>*>(&va);
//--------------------------------------------------------------------------//
// HERE ENDS THE UGLY HACK.
//--------------------------------------------------------------------------//
//---
double dummy = 0.0; // this is only relevant for the code used as minimum example
func_in_lib(*p_vec_access, dummy);
//--- print the modified data
for (int i = 0; i < count; ++i)
cout << my_data.get()[i];
return 0;
}
Обновление: Анализ кода ассемблера второго решения показывает, что копирование содержимого выполняется, даже если конструктор копирования объектов данных не вызывается. Процесс копирования происходит на уровне машинного кода.
Решение 2. Перемещение semanti c
Для этого решения я должен отметить Move-Constructor DataType
с помощью noexcept
. Ключевая идея - не рассматривать массив DataType[]
как std::vector<double>
. Вместо этого мы рассматриваем std::vector<double>
как std::vector<DataType>
. Затем мы можем переместить данные в этот вектор (без копирования), отправить их в функцию и потом переместить обратно.
Данные не копируются, а перемещаются std::vector
, что быстрее. Также актуально для моего случая, я снова могу взять часть исходных данных, хранящихся в моем сложном классе. Недостаток этого решения: мне нужно создать дополнительное хранилище для перемещенных данных правильного размера.
#include <iostream>
#include <memory>
#include <utility>
#include <vector>
using namespace std;
//--------------------------------------------------------------------------//
//--- This function is given, I cannot change its signature.
bool
func_in_lib(std::vector<double>& data, double& res) {
//--- check some properties of the vector
if (data.size() < 10)
return false;
//--- do something magical with the data
for (auto& d : data)
d *= 2.0;
res = 42.0;
return true;
}
//--------------------------------------------------------------------------//
class DataType {
public:
double a = 1.0;
double b = 2.0;
double c = 3.0;
// clang-format off
DataType() = default;
DataType(DataType const&) = default;
DataType(DataType&&) noexcept = default;
DataType& operator=(DataType const&) = default;
DataType& operator=(DataType&&) noexcept = default;
~DataType() = default;
// clang-format on
};
//--------------------------------------------------------------------------//
ostream&
operator<<(ostream& out, const DataType& d) {
out << d.a << " " << d.b << " " << d.c << endl;
return out;
}
//--------------------------------------------------------------------------//
int
main(int argc, char const* argv[]) {
int count = 20;
//--- init and print my data
unique_ptr<DataType[]> my_data = make_unique<DataType[]>(count);
for (int i = 0; i < count; ++i)
cout << my_data.get()[i];
//---
vector<double> double_vec;
double_vec.reserve(count * 3);
//--- here starts the magic stuff
auto& vec_as_datatype = *reinterpret_cast<vector<DataType>*>(&double_vec);
auto* start_mv = &(my_data.get()[0]);
auto* stop_mv = &(my_data.get()[count]) + 1;
//--- move the content to the vec
move(start_mv, stop_mv, back_inserter(vec_as_datatype));
//--- call the external func in the lib
double dummy = 0.0; // is only needed for the code of the example
func_in_lib(double_vec, dummy);
//--- move the content to back
move(begin(vec_as_datatype), end(vec_as_datatype), start_mv);
//--- print modified the data
for (int i = 0; i < count; ++i)
cout << my_data.get()[i];
}