У меня есть простой класс-обертка вокруг массива в стиле C. Я не хочу использовать std::vector, так как хочу иметь только один массив, даже если я скопирую struct. При использовании std::vector вектор также копируется.
struct RawDataArray {
double* data;
size_t size;
static RawDataArray CreateNew(size_t size){
return RawDataArray(new double[size], size);
}
static RawDataArray CreateNew(size_t size, double defaultValue){
RawDataArray rd = RawDataArray::CreateNew(size);
std::fill_n(rd.data, size, defaultValue);
return rd;
}
static RawDataArray CreateCopy(double* data, size_t size) {
RawDataArray rd = RawDataArray::CreateNew(size);
std::copy(data, data + size, rd.data);
return rd;
}
RawDataArray(double* data, int size) :
data(data),
size(size)
{}
RawDataArray(const RawDataArray& other) :
data(other.data),
size(other.size)
{}
RawDataArray(RawDataArray&& other) noexcept :
data(std::exchange(other.data, nullptr)),
size(std::exchange(other.size, 0))
{}
RawDataArray& operator=(const RawDataArray& other){
return *this = RawDataArray(other);
}
RawDataArray& operator=(RawDataArray&& other) noexcept {
std::swap(data, other.data);
std::swap(size, other.size);
return *this;
}
}
Однако проблема заключается в управлении памятью. Если я перетасую класс-обертку, я не знаю, смогу ли я безопасно освободить данные.
Я думал использовать std::shared_ptr<double[]>, который доступен с C++17 для массивов. Есть ли другой способ или идиома, которую можно использовать?
Безусловно, самый простой способ сделать это — использовать std::shared_ptr<double[]>
вместо необработанного указателя double*
. Все будет Просто Работать™, и вам не придется думать о копирующих и перемещающих конструкторах или деструкторах (сравните правило нуля).
Могут быть веские причины не использовать shared_ptr
. Например, shared_ptr
должен использовать атомарный подсчет ссылок, чтобы быть потокобезопасным, даже если ваш код никогда не будет совместно использовать массив между потоками. Это может быть немного медленнее, в зависимости от платформы. А для shared_ptr
обычно требуется два выделения: одно для контрольного блока со счетчиком ссылок и средством удаления, другое для фактических данных. Хотя shared_ptr
это необходимо для поддержки эффективных слабых ссылок и из-за деталей стандарта C++, ваша реализация языка C++ может позволить вам использовать более компактное представление с использованием гибкого члена массива в стиле C. Тогда примерно:
struct Inner {
size_t refcount;
size_t capacity;
double data[]; // !!! not ISO C++ !!!
};
struct SharedDataArray {
Inner* inner;
SharedDataArray() : inner(nullptr) {}
explicit SharedDataArray(size_t capacity) : SharedDataArray() {
if (!capacity) return;
inner = reinterpret_cast<Inner*>(std::malloc(
sizeof(Inner) + capacity * sizeof(double)
));
if (!inner) throw std::bad_alloc();
inner->refcount = 1;
inner->capacity = capacity;
std::fill(inner->data, inner->data + capacity, 0);
}
SharedDataArray(SharedDataArray& other) : inner(other.inner) {
if (!inner) return;
++(inner->refcount);
}
~SharedDataArray() {
if (!inner) return;
--(inner->refcount);
if (inner->refcount > 0) return;
std::free(inner);
inner = nullptr; // not strictly necessary
}
};
Но это сложно сделать правильно, и вам действительно стоит подумать об использовании вместо этого shared_ptr
. Предпочтительно shared_ptr<vector<double>>
, поскольку он управляет жизненным циклом массива за вас за счет дополнительной косвенности указателя.