LINUX.ORG.RU

Массив для хранения 2-х типов данных

 


0

2

Имеются два класса, первый хранит в себе пару значений, а второй четвёрку; второй наследуется от первого.

class A{
	T a;
	T b;
};
class B: A{
	T c;
	T d;
};
Хочу сделать класс массива, в котором в начале хранятся значения A, а в конце B:
{ A1, A2, A3, A4, ...Ai, ... , Bj... , B3, B2, B1 }
как такое лучше реализовать?

★★★★★

Ответ на: комментарий от tailgunner

Нехорошо, так как слишком много пустых ячеек будет. Задача такова, что в одних случаях i≫j, а в других j≫i (примерно в 10 раз).

thunar ★★★★★
() автор топика
Последнее исправление: thunar (всего исправлений: 1)

Вот оно, прокрустово ложе статической типизации )) Или лепить тип-сумму, или взять динамический язык - типа Java например, где можно определить массив ListArray<Object> а потом разгребать его по instance of...

Ivana
()
Ответ на: комментарий от tailgunner

Массив не растёт в размере, один раз выделяю память с запасом. Соответственно, не все ячейки массива хранят полезные данные. Всреднем где то ~1/3 не используется.

thunar ★★★★★
() автор топика
Последнее исправление: thunar (всего исправлений: 2)
Ответ на: комментарий от Ivana

Вот оно, прокрустово ложе статической типизации ))

Мимо

или взять динамический язык - типа Java

Не советуй дурного.

Kroz ★★★★★
()
Ответ на: комментарий от mersinvald

А как с типами быть? Например, я выделил память char *p = new char [N], pS — указатель на его начало, pE — указатель на его последний байт. Тогда указатель на элемент Ai = pS+i*sizeof(A), а Bj=pE-(j-1)*sizeof(B). Что бы это выражение прошло проверку типов следует использовать что ни будь вроде:

A *someA = reinterpret_cast<A*>(pS+i*sizeof(A));
B *someB = reinterpret_cast<B*>(pE-(j-1)*sizeof(B));
так?

thunar ★★★★★
() автор топика

Не понял.

1. Допустим у тебя есть данные A1(1,2), A2(3.4), A3(5,6), B1(7,8,9,10), B2(11,12,13,14). Ты хочешь хранить это как { (1,2), (3,4), (5,6), (11,12), (13,14), (7,8), (9,10)}? Или как? Покажи ожидаемый массив.

2. Какова цель? Сэкономить память? Готов сделать это в ущерб CPU?

3. И для чего конкретно ты это делаешь?

Kroz ★★★★★
()

Хочу сделать класс массива

То есть перегрузить operator[], чтобы возвращал A&? Ну тогда выделять сырую память нужного размера, обмазываться арифметикой с sizeof и кастами. Но для начала стоит задуматься, действительно ли оно нужно.

Softwayer ★★
()
Ответ на: комментарий от Kroz

Покажи ожидаемый массив

{A1, A2, ... , B2, B1} <=> {A1.1, A1.2, A2.1, A.2.2, (всякий мусор), B2.1, B2.2, B2.3, B2.4, B1.1, B1.2, B1.3, B1.4}

Сэкономить память?

Да, таких массивов будет много, могу не влезть в оперативку. А сам массив содержит немного элементов и влезает в кеш CPU.

Готов сделать это в ущерб CPU?

На каком этапе тормоза вылезут? Из за невыровненности данных? У меня sizeof(A)=32, sizeof(B)=64.

И для чего конкретно ты это делаешь?

PiC/DSMC моделирование разряда с сильным ионизационным выгоранием. Тип A — атомы, тип B — ионы/электроны. В норме количество атомов на порядок больше ионов/электронов, но в ряде ситуаций весь газ перерабатывается плазму, соответственно, приходится закладывать для неё столько же ячеек сколько и для атомов. Получается 3х-кратный перерасход памяти.

thunar ★★★★★
() автор топика
Последнее исправление: thunar (всего исправлений: 2)
Ответ на: комментарий от Ivana

Java например, где можно определить массив ListArray<Object> а потом разгребать его по instance of...

Как будто в плюсах так нельзя.

devsdc ★★
()
Ответ на: комментарий от thunar

Массив не растёт в размере, один раз выделяю память с запасом.
с запасом

Да, таких массивов будет много, могу не влезть в оперативку.

Неужто делать так, а потом возиться с тем, что предложат в этом треде, действительно лучше, чем делать два разных массива нужных (динамически определяемых) размеров?

Ну или сделать один массив типа B, но первую часть использовать только как A.

Тут проблема-то не только с тем, как сделать такой массив, но и с тем, как оно потом будет использоваться. Обращение к элементу массива arr[i] компилируется во что-то вроде *(arr + sizeof(element_size) * i), где element_size известно на момент компиляции. Ты же хочешь в одном массиве хранить элементы разных размеров, и определять тип элемента (а значит, и его размер) динамически. В этом случае указанная схема работать уже не будет.

devsdc ★★
()
Последнее исправление: devsdc (всего исправлений: 2)
Ответ на: комментарий от devsdc

где element_size известно на момент компиляции. Ты же хочешь в одном массиве хранить элементы разных размеров, и определять тип элемента (а значит, и его размер) динамически.

Для этого отдельные методы .getA(i), .getB(j), через [] обращения не будет.

и определять тип элемента (а значит, и его размер)

нет, я же храню индексы и знаю в какой области лежит один тип, а в какой — другой.

два разных массива нужных (динамически определяемых) размеров?

можно минимальный пример?

Ну или сделать один массив типа B, но первую часть использовать только как A.

это как?

thunar ★★★★★
() автор топика
Последнее исправление: thunar (всего исправлений: 4)
Ответ на: комментарий от thunar

Для этого отдельные методы .getA(i), .getB(j), через [] обращения не будет.

А, так ты хочешь не прямо массив, а обёртку.

Ну или сделать один массив типа B, но первую часть использовать только как A.

это как?

Ну, что-то типа такого (на ходу накалякал прямо тут, даже синтаксис не проверял):

struct Arr
{
    Arr(size_t size)
    : elements(new B[size])
    {}

    A& getA(size_t i)
    { return static_cast<A&>(*elements[i]); }

    B& getB(size_t j)
    { return *elements[j]; }

    B * elements;
};

«использовать как A» - давать элемент пользователю как элемент типа A, не давая ему знать, что на самом деле это B и занимает лишние несколько байт. Но если хочется делать реально одним массивом, то ты от этих лишних байт никуда и не денешься, потому что описанное в предыдущем комменте обращение к массиву по operator[].

devsdc ★★
()
Ответ на: комментарий от thunar

Вообще, раз уж ты обертку пилишь, то очевидное решение будет таким:

struct Arr
{
    Arr(size_t a_size, size_t b_size)
    : as(new A[a_size])
    , bs(new B[b_size])
    {}

    A& getA(size_t i)
    { return *as[i]); }

    B& getB(size_t j)
    { return *bs[j]; }

    A * as;
    B * bs;
};

И я действительно не понимаю, что тебя в этом решении не устраивает.

devsdc ★★
()
Ответ на: комментарий от thunar

На каком этапе тормоза вылезут?

На этапе преобразования индексов.
На самом деле я поддерживаю идею двух массивов.
но если хочешь извращений, то как-то так (пишу навскидку, код не проверял, идею, думаю, поймешь)

class A{
  T a;
  T b;
};
class B: A{
  T c;
  T d;
  
  A GetA1(){ return A(a,b); }
  A GetA2(){ return A(c,d); }

  B();
  B(A A1, A A2):A(A1),c(A2.a),d(A2.b){};
};

class Container{
  std::vector<A> arr[size]; // Сам определи как ты размер задашь
  A GetA(int i){ return arr[i];}
  B GetB(int i){ return B(arr[arr.size()-2-i*2],arr[arr.size()-1-i*2]); }
}

Говнокод. Сам проставишь константы, сделаешь чтобы лишний раз a, b, c, d не копировались, а возвращались ссылками. Но идею, думаю, ты понял.
Еще:
- изменение размера массива создаст еще накладные расходы
- надеюсь, ты проконтроллируешь, чтобы одна половина массива не налезла на другую.
- коль ты пишешь на C++, настоятельно рекомендую использовать vector вместо C-шных массивов.

Kroz ★★★★★
()
Последнее исправление: Kroz (всего исправлений: 2)

сделай два вектора, и просто храни в них указатели. Если указатель == NULL значит тебе пора искать во втором векторе этот же элемент. На указатели не так и много памяти улетит, коль тебе ее жалко.

matrixd
()

значения A, а в конце B

вообще не прочел. Держи два вектора, и не имей мозги. Я понимаю если бы они вперемешку шли...

matrixd
()
Последнее исправление: matrixd (всего исправлений: 1)

Хочу сделать класс массива, в котором в начале хранятся значения A, а в конце B:

И на кой хрен тебе это надо, почему нельзя просто массив указателей? Если тебе так прям нужна компактность данных сделай класс который будет хранить 2 массива и предоставлять прозрачный доступ через перегрузку operator[].

no-such-file ★★★★★
()
Ответ на: комментарий от Kroz

Так, пока получилось что то вроде (класс из 4-х элементов хранится в начале, класс из 2-х — в конце):

class CPool{
public:
	size_t index_e=0, buffer_e=0;
	size_t index_i=0, buffer_i=0;
	size_t index_0=0, buffer_0=0;
private:
	byte *pool_s;
	byte *pool_e;
public:
	CPool(size_t n){ //n bytes
		pool_s= new byte[n];
		pool_e= pool_s +(n);
		std::cout<<(size_t)pool_s<<":"<<(size_t)pool_e<<"("<<(size_t)(pool_e-pool_s)/1024<<"KiB)"<<std::endl;
	}
	~CPool(){
		delete[]pool_s; pool_s=NULL; pool_e=NULL;
	}
	// management --------------------------------------------------------------//
	#define _NE( n ) (pool_s + (((n)<<1)+0)*sizeof(CCharged)) //electrons 64 bytes
	#define _NI( n ) (pool_s + (((n)<<1)+1)*sizeof(CCharged)) //ions      64 bytes
	#define _N0( n ) (pool_e - (((n)   )+1)*sizeof(CNeutral)) //neutrals  32 bytes
	CCharged *get_e(size_t n){
		return reinterpret_cast<CCharged*>_NE(n);
	}
	CCharged *get_i(size_t n){
		return reinterpret_cast<CCharged*>_NI(n);
	}
	CNeutral *get_0(size_t n){
		return reinterpret_cast<CNeutral*>_N0(n);
	}
	CCharged *add_e(){
		if( _NE(index_e)<_N0(index_0) ){
			return get_e(index_e++);
		} else return NULL;
	}
	CCharged *add_i(){
		if( _NI(index_i)<_N0(index_0) ){
			return get_i(index_i++);
		} else return NULL;
	}
	CNeutral *add_0(){
		if( _N0(index_0)>_NE(index_e) && _N0(index_0)>_NI(index_i) ){
			return get_0(index_0++);
		} else return NULL;
	}
	void del_e(CCharged *p){
		if(index_e){
			index_e--;
			if(p!=get_e(index_e)) *p= *get_e(index_e);
		}
	}
	void del_i(CCharged *p){
		if(index_i){
			index_i--;
			if(p!=get_i(index_i)) *p= *get_i(index_i);
		}
	}
	void del_0(CNeutral *p){
		if(index_0){
			index_0--;
			if(p!=get_0(index_0)) *p= *get_0(index_0);
		}
	}
	#undef _NE
	#undef _NI
	#undef _NA
	// end management ----------------------------------------------------------//
};

thunar ★★★★★
() автор топика
Последнее исправление: thunar (всего исправлений: 1)
Ответ на: комментарий от grondek

Массив указателей на базовый класс.

С точки зрения экономии памяти очень классно рядом с элементами (коих много) хранить еще и указатели на них.

Kroz ★★★★★
()
Последнее исправление: Kroz (всего исправлений: 1)

Насколько я могу судить, пока предложение mersinvald лучшее.

tailgunner ★★★★★
()
Ответ на: комментарий от Deleted

Видимо поэтому в плюсы запилили шаблоны. Ведь если название другое дать, то всё изменится.

anonymous
()
Ответ на: комментарий от thunar
...
pool_s= new byte[n];
... 
#define _NE( n ) (pool_s + (((n)<<1)+0)*sizeof(CCharged)) //electrons 64 bytes
...
CCharged *get_e(size_t n){
		return reinterpret_cast<CCharged*>_NE(n);
	}
...


Шутишь?
Допустим у тебя 10 элементов (0..9), ты хочешь взять последний. Тогда (((n)<<1)+0)*sizeof(CCharged))=2*9*64=82 - ты явно вылезаешь за границы массива (pool_s= new byte[n];)

И прекрати использовать указатели! В С++ в 99% случаев это просто не нужно, а указатели - потенциальный источник проблем.
Срочно читать про std::vector, хотябы тут: http://cppstudio.com/post/8453/

Еще:
- byte - где ты взял такой тип? uint8_t
- у тебя _NE() и _NI() идентичны. Ты в программе проконтроллируешь, чтобы не ошибиться типом элементов? Я бы тебе очень рекомендовал выделиьт хотябы 2 бита на идентификацию типа и контроллировать это.

Kroz ★★★★★
()
Ответ на: комментарий от Kroz

И прекрати использовать указатели! В С++ в 99% случаев это просто не нужно

Возможно, у нас случай из 1%.

Срочно читать про std::vector

Всегда умиляют фразы, построенные таким образом. Как будто никто, кроме говорящего, не читал про vector.

tailgunner ★★★★★
()

ты хочешь в одном массиве(фактически куске памяти) завести стеки растущие по напровлению друг другу с элементами типа характерными для каждого стека в отдельности?

ну так и сделай

если размеры следов классов не сильно различны то можно

union{ class A X; class B Y; }

qulinxao ★★☆
()

pair<vector<A>, vector<B>>

не?

anonymous
()
Ответ на: комментарий от tailgunner

Возможно, у нас случай из 1%.

Нет. Посмотри на код.

Всегда умиляют фразы, построенные таким образом. Как будто никто, кроме говорящего, не читал про vector.

Не поверишь, но я видел уйму людей, которые под «я пишу С++» понимали только использование классов, а всё остальное у них было чистый C.

Kroz ★★★★★
()
Ответ на: комментарий от Kroz

Возможно, у нас случай из 1%.

Нет. Посмотри на код.

ТС просто сбили с пути. Если я правильно понимаю и он знает суммарный объем в байтах, но не знает его конкретного распределения между типами объектов. Тогда правильный ответ: Массив для хранения 2-х типов данных (комментарий) Правда, реализация странноватая на первый взгляд.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

и даже если так, низлежащий массив всё равно лучше как vector<char> делать, а там и указатели оказываются не нужны

anonymous
()
Ответ на: комментарий от qulinxao

ты хочешь в одном массиве(фактически куске памяти) завести стеки растущие по напровлению друг другу с элементами типа характерными для каждого стека в отдельности?

Ну не совсем стек, но близко к тому. Доступ к произвольному элементу, а при удалении, на его место кладётся вершина. И да, 2 типа данных с двух сторон:

[aaa..bb]~1MB
Смысл в том, что есть один большой кусок памяти, на него буду маппить эти массивчики, так что бы было:
{ [aaa...b] [aaa..bb] [aa....b] [a.....b] [aaa.bbb] ... [a...bbb] [aaa...b] [aaabbb] }~1\div5\div30GB(в зависимости от размерности задачи) 
Нужно что бы в переделах одного [aaa..bb] массивчика данные лежали рядом дабы минимизировать промахи кеша.

thunar ★★★★★
() автор топика
Последнее исправление: thunar (всего исправлений: 1)
Ответ на: комментарий от anonymous

Но в чем профит? vector имеет смысл, когда у тебя типизированный контейнер, а мы тупо пытаемся сделать массив байтов.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Если я правильно понимаю и он знает суммарный объем в байтах, но не знает его конкретного распределения между типами объектов.

Да, знаю суммарный обём, он статичен и не меняется. А распределение между типами определяется индексами, оно меняется.

Массив для хранения 2-х типов данных (комментарий)

Ну вот сейчас я вроде через 2 указателя и сделал, один — на начало, другой — на конец оборачиваемого массива. При добавлении элемента проверяем что бы адрес не вылез за пределы соседа, при обращении это не нужно, так как индексы известны.

thunar ★★★★★
() автор топика
Последнее исправление: thunar (всего исправлений: 1)
Ответ на: комментарий от thunar

Ну вот сейчас я вроде через 2 указателя и сделал,

В принципе правильно, но я споткнулся на pool_* и перестал смотреть после макросов со сдвигами.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Тогда правильный ответ: Массив для хранения 2-х типов данных (комментарий)

Не учтено то, что человек хочет экономить память. А с таким условием делать указатель на каждый элемент, когда их много, нельзя.

Более правильный - вот этот: Массив для хранения 2-х типов данных (комментарий)

Мой key message: если всё же ТС настаивает на одном массиве (имеет право), то его код в лучшем случае вылетит с segmentation fault, так как там налицо выход за границы массива. И он мог бы защититься от выстрела в ногу просто используя std::vector. И я не вижу причин не использовать std::vector.

Kroz ★★★★★
()
Ответ на: комментарий от thunar

знаю суммарный обём
А распределение между типами определяется индексами, оно меняется.

Тогда ты не знаешь сумарный объем памяти, так как типы имеют разную величину.

Kroz ★★★★★
()
Ответ на: комментарий от anonymous

Шаблоны в C++ – это далеко не венец творения человека.

Deleted
()
Ответ на: комментарий от tailgunner

профит в том, чтобы исключение в конструкторе не приводило к утечкам памяти

anonymous
()
Ответ на: комментарий от Kroz

если всё же ТС настаивает на одном массиве (имеет право), то его код в лучшем случае вылетит с segmentation fault

Это всего лишь дело достаточного количества проверок.

tailgunner ★★★★★
()
Ответ на: комментарий от next_time

Да. Суммарное количество может быть вообще равно нулю, например, в самом начале счёта.

thunar ★★★★★
() автор топика

Виртуальная функция. Оверхэд - размер указателя на vtable.

Deleted
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.