Я пытаюсь создать общую функцию, которая будет динамически выделять 2D-структуру. Количество элементов в каждой строке не обязательно должно быть одинаковым для всех строк. Структура представлена как тип контейнера, элементы которого также имеют тип контейнера (например, набор списков). Тип элементов этого внутреннего контейнера также может быть произвольным. Контейнеры поддерживают только функции начала, конца и размера. Операции итератора должны поддерживаться для всех типов итераторов. Функция должна сначала динамически выделить место для хранения 2D-структуры методом непрерывного выделения, а затем переписать принятые ею элементы структуры в динамическую структуру. Функция возвращает двойной указатель, по которому можно получить доступ к элементам этой структуры.
#include <iostream>
#include <set>
#include <list>
#include <vector>
template < typename tip >
auto Make2DStructure(tip mat) {
using tip_objekta = typename std::decay < decltype(mat[0][0]) > ::type;
tip_objekta ** dynamic_mat = nullptr;
int rows = 0, total = 0;
for (auto i: mat) {
rows++;
for (auto j: i)
total++;
}
int columns[rows];
int k = 0;
for (auto i: mat) {
int num_of_colums = 0;
for (auto j: i)
num_of_colums++;
columns[k] = num_of_colums;
k++;
}
try {
dynamic_mat = new tip_objekta * [rows];
dynamic_mat[0] = new tip_objekta[total];
for (int i = 1; i < rows; i++)
dynamic_mat[i] = dynamic_mat[i - 1] + columns[i];
for (int i = 0; i < rows; i++)
for (int j = 0; j < columns[i]; j++)
dynamic_mat[i][j] = mat[i][j];
} catch (...) {
delete[] dynamic_mat[0];
delete[] dynamic_mat;
throw std::bad_alloc();
}
return dynamic_mat;
}
int main() {
std::vector<std::vector<int>>mat{
{1,2},
{3,4,5,6},
{7,8,9}
};
int columns[3] = {2,4,3};
try {
int ** dynamic_mat = Make2DStructure(mat);
for (int i = 0; i < 3; i++) {
for (int j = 0; j < columns[i]; j++)
std::cout << dynamic_mat[i][j] << " ";
std::cout << std::endl;
}
delete[] dynamic_mat[0];
delete[] dynamic_mat;
} catch (...) {
std::cout << "Problems with memory";
}
return 0;
}
Как я могу изменить это, чтобы оно работало без индексации внутри Make2DStrucure()
?
Кроме того, если бы я использовал std::set<std::list<int>>
вместо std::vector<std::vector<int>>
в основной функции, у меня были бы проблемы с дедукцией. Как я могу изменить это для работы с разными внешними и внутренними контейнерами?
Это может быть очередь списков или вектор вектора, но дело в том, что я не знаю, какие типы контейнеров они будут. Функция является общей
Хорошо, тогда, возможно, я неправильно понял. В чем причина возврата необработанного указатель-указатель вместо чего-то, что автоматически управляет временем жизни?
это настройка задачи, используется двойной указатель из-за динамического распределения
Делайте то, что требует от вас назначающий (если это для профессиональной работы, делайте все возможное, чтобы отговорить их от этого), но знайте, что передача необработанных указателей — это не то, как вы должны управлять динамическим выделением памяти в современном C++ (или любом другом C++). после стандартизации в 1998 г.).
спасибо, но вы заметили, что у меня есть некоторые проблемы внутри кода? Я согласен с тем, что двойной указатель не является хорошим выбором, но я не могу его скомпилировать из-за проблем типа вывода. Если бы я использовал вектор вектора в основной функции, у меня не было бы проблем с дедукцией, но в обоих случаях я не знаю, как считать строки и столбцы рваной матрицы.
Я отредактировал код, теперь он работает для вектора вектора. Как я могу изменить это, чтобы он работал с набором списков или очередью списка или очередью векторов? типы контейнеров поддерживают только функции std::begin(), std::end() и size().
Я не уверен, что именно вам нужно. Делает ли это это?
это дает правильный результат, но я не знаю, использовали ли вы для этого постоянное распределение? Если да, не могли бы вы опубликовать это как ответ с некоторыми комментариями? Кроме того, я использую C++14, поэтому не могли бы вы изменить его для C++14?
@Ted: посмотрите, можете ли вы использовать std::begin
, чтобы вы также могли использовать необработанные массивы.
@ user4581301 Хорошая идея: godbolt.org/z/ejjjrWGjK
@TedLyngmo, если это постоянное распределение, не могли бы вы опубликовать ответ на C++ 14 с некоторыми комментариями?
@RocketProcd Хорошо, я опубликовал версию C++14 с комментариями.
Вот один из способов добиться того, что вы хотите:
#include <iterator>
#include <type_traits>
template <typename tip>
auto Make2DStructure(tip&& mat) {
// create an alias for the value type:
using value_type = std::decay_t<decltype(*std::begin(*std::begin(mat)))>;
// allocate memory for the return value, the pointer-pointer:
value_type** rv = new value_type*[mat.size()]; // C++17: std::size(mat)
// Calculate the number of values we need to allocate space for:
size_t values = 0;
for(auto& inner: mat) values += inner.size(); // C++17: std::size(inner)
// allocate the space for the values:
value_type* data = new value_type[values];
// loop over the outer and inner container and keep the index running:
size_t idx = 0;
for(auto& inner : mat) {
// assign the outer pointer into the flat data block:
rv[idx++] = data;
for(auto& val : inner) {
// assign values in the data block:
*data++ = val;
}
}
return rv;
}
С использованием std::size
, где указано, это будет работать и с простыми массивами, а не только с классами контейнеров.
Во-первых, индексация внутри Make2DStructure() не должна поддерживаться, есть ли способ изменить это с помощью итераторов вместо индексации?
@RocketProcd Прямо сейчас знания о размерах каждого «внутреннего» массива теряются при переводе в формат int**
, но я думаю, что для этой конкретной настройки можно создать специальный тип итератора. Однако вам нужно будет хотя бы предоставить некоторую информацию итератору end
для внешнего массива. Его нельзя вывести из информации, хранящейся в int**
.
есть ли способ уменьшить это std::remove_cv_t<std::remove_reference_t< decltype(*std::begin(*std::begin(std::declval<tip>())))>>;
?
@RocketProcd Вы можете использовать фактический экземпляр вместо std::declval<tip>()
, что сократит его до std::remove_cv_t<std::remove_reference_t< decltype(*std::begin(*std::begin(mat)))>>;
. Вы также можете использовать функции-члены begin()
вместо std::begin()
, поскольку вам все равно требуется функция-член size()
. Если бы вы могли использовать C++17, я бы использовал std::size(mat)
вместо mat.size()
, чтобы также иметь возможность отправлять простые массивы, но тогда мне нужно было бы добавить шаблон функции size
, чтобы обеспечить ту же функциональность.
спасибо, хоть немного меньше
Я нашел другой способ using value_type = typename std::decay<decltype(*std::begin(*std::begin(mat)))>::type;
@RocketProcd О, да, это на самом деле возвращается к std::remove_cv_t<std::remove_reference_t< ...
, так что все будет хорошо. Вы даже можете использовать помощник std::decay_t
в C++14, чтобы сделать его короче. Я обновил ответ.
Это звучит точно так же, как
std::vector<std::vector<T>>
. Вы пробовали это использовать?