Как каждый подавляет автоматическую инициализацию и разрушение типа? В то время как это замечательно это T buffer[100]
автоматически инициализирует все элементы buffer
, и уничтожает их, когда они падают из объема, это не поведение, которое я хочу.
#include <iostream>
static int created = 0,
destroyed = 0;
struct S
{
S()
{
++created;
}
~S()
{
++destroyed;
}
};
template <typename T, size_t KCount>
class fixed_vector
{
private:
T m_buffer[KCount];
public:
fixed_vector()
{
// some way to suppress the automatic initialization of m_buffer
}
~fixed_vector()
{
// some way to suppress the automatic destruction of m_buffer
}
};
int main()
{
{
fixed_vector<S, 100> arr;
}
std::cout << "Created:\t" << created << std::endl;
std::cout << "Destroyed:\t" << destroyed << std::endl;
return 0;
}
Вывод этой программы:
Created: 100
Destroyed: 100
Я хотел бы, чтобы это было:
Created: 0
Destroyed: 0
Моя единственная идея состоит в том, чтобы сделать m_buffer
некоторый тривиально созданный и разрушенный тип как char
и затем полагайтесь operator[]
переносить математику указателя для меня, хотя это походит на ужасно взломанное решение. Другое решение состояло бы в том, чтобы использовать malloc
и free
, но это дает уровень абстракции, который я не хочу.
Причина, почему я хочу это, состоит в том, потому что я делаю контейнер, и я не хочу платить за инициализацию наверху вещей, которые я не буду использовать. Например, если мой main
функция была:
int main()
{
{
std::vector<S> vec;
vec.reserve(50);
}
std::cout << "Created:\t" << created << std::endl;
std::cout << "Destroyed:\t" << destroyed << std::endl;
return 0;
}
Вывод был бы корректен:
Created: 0
Destroyed: 0
Вы можете создать массив как массив из char
s, а затем использовать размещение new
для создания элементов при необходимости.
template <typename T, size_t KCount>
class Array
{
private:
char m_buffer[KCount*sizeof(T)]; // TODO make sure it's aligned correctly
T operator[](int i) {
return reinterpret_cast<T&>(m_buffer[i*sizeof(T)]);
}
После перечитывания вашего вопроса кажется, что вам нужен разреженный массив, иногда он называется map ; o) (конечно, характеристики производительности другие ...)
template <typename T, size_t KCount>
class SparseArray {
std::map<size_t, T> m_map;
public:
T& operator[](size_t i) {
if (i > KCount)
throw "out of bounds";
return m_map[i];
}
Вы можете изучить boost :: optional
template <typename> struct tovoid { typedef void type; };
template <typename T, size_t KCount, typename = void>
struct ArrayStorage {
typedef T type;
static T &get(T &t) { return t; }
};
template <typename T, size_t KCount>
struct ArrayStorage<T, KCount, typename tovoid<int T::*>::type> {
typedef boost::optional<T> type;
static T &get(boost::optional<T> &t) {
if(!t) t = boost::in_place();
return *t;
}
};
template <typename T, size_t KCount>
class Array
{
public:
T &operator[](std::ptrdiff_t i) {
return ArrayStorage<T, KCount>::get(m_buffer_[i]);
}
T const &operator[](std::ptrdiff_t i) const {
return ArrayStorage<T, KCount>::get(m_buffer_[i]);
}
mutable typename ArrayStorage<T, KCount>::type m_buffer_[KCount];
};
. Для типа класса выполняется специализация, которая превращает их в optional
, таким образом вызывая конструктор / деструктор лениво. Для неклассовых типов такая упаковка нам не нужна. Их отсутствие в оболочке означает, что мы можем рассматривать & a [0]
как непрерывную область памяти и передавать этот адрес функциям C, которым нужен массив. boost :: in_place
создаст типы классов на месте, без использования временного T
или его конструктора копирования.
Отсутствие наследования или закрытых членов позволяет классу оставаться агрегатом, что позволяет использовать удобную форму инициализации
// only two strings are constructed
Array<std::string, 10> array = { a, b };
Если вы хотите быть похожими на вектор, вы должен делать что-то вроде этого:
template <typename T>
class my_vector
{
T* ptr; // this is your "array"
// ...
public:
void reserve(size_t n)
{
// allocate memory without initializing, you could as well use malloc() here
ptr = ::operator new (n*sizeof(T));
}
~my_vector()
{
::operator delete(ptr); // and this is how you free your memory
}
void set_element(size_t at, const T& element = T())
{
// initializes single element
new (&ptr[at]) T(element); // placement new, copies the passed element at the specified position in the array
}
void destroy_element(size_t at)
{
ptr[at].~T(); // explicitly call destructor
}
};
Этот код, очевидно, предназначен только для демонстрации, я пропустил конструктор копирования my_vector и любое отслеживание того, что создано, а что нет (вызов деструктора в месте, для которого вы не вызывали конструктор, вероятно, является неопределенным поведением ). Кроме того, выделение и освобождение вектора STL абстрагируется с помощью распределителей (второй аргумент шаблона
вектора
).
Надеюсь, что это поможет
Вы можете посмотреть, как это делается с контейнерами STL, но я сомневаюсь, что вы сможете сэкономить себе malloc
и free
Этот код:
#include <iostream>
#include <vector>
using namespace std;
int created = 0, destroyed = 0;
struct S
{
S()
{
++created;
}
S(const S & s ) {
++created;
}
~S()
{
++destroyed;
}
};
int main()
{
{
std::vector<S> vec;
vec.reserve(50);
}
std::cout << "Created:\t" << created << std::endl;
std::cout << "Destroyed:\t" << destroyed << std::endl;
return 0;
}
дает именно тот результат, который вам нужен - я не уверен, в чем ваш вопрос.