Я уже давно пытаюсь реализовать функцию length (T * v), но пока безуспешно.
Существует два основных, хорошо известных решения для T v [n] массивов, оба из которых бесполезны или даже опасны после того, как массив был преобразован в указатель T * v.
#define SIZE(v) (sizeof(v) / sizeof(v[0]))
template <class T, size_t n>
size_t lengthof (T (&) [n])
{
return n;
}
Существуют обходные пути, включающие классы-оболочки и контейнеры, такие как array_proxy STLSoft, boost :: array,std :: vector и т. д. Все они имеют недостатки, и им не хватает простоты, синтаксического сахара и широкого использования массивов.
Существуют мифы о решениях, включающих вызовы, специфичные для компилятора, которые обычно используются компилятором, когдаdelete [] должен знать длину массива.В соответствии с C ++ FAQ Lite 16.14, компиляторы используют два метода для определения объема выделяемой памяти: перераспределение и ассоциативные массивы.При перераспределении он выделяет еще один размер слова и помещает длину массива перед первым объектом.Другой метод, очевидно, хранит длины в ассоциативном массиве.Можно ли узнать, какой метод использует G ++, и извлечь соответствующую длину массива?А как насчет накладных расходов и прокладок?Есть надежда на некомпиляторный код?Или даже не для платформ, встроенных в G ++?
Есть также решения, включающие перегрузку оператора new [] и оператора delete [], которые я реализовал:
std::map<void*, size_t> arrayLengthMap;
inline void* operator new [] (size_t n)
throw (std::bad_alloc)
{
void* ptr = GC_malloc(n);
arrayLengthMap[ptr] = n;
return ptr;
}
inline void operator delete [] (void* ptr)
throw ()
{
arrayLengthMap.erase(ptr);
GC_free(ptr);
}
template <class T>
inline size_t lengthof (T* ptr)
{
std::map<void*, size_t>::const_iterator it = arrayLengthMap.find(ptr);
if( it == arrayLengthMap.end() ){
throw std::bad_alloc();
}
return it->second / sizeof(T);
}
Это работало хорошо доЯ получил странную ошибку: lengthof не может найти массив.Как оказалось, G ++ выделил на 8 байтов больше в начале этого конкретного массива, чем следовало бы.Хотя оператор new [] должен был вернуть начало всего массива, назовите его ptr, вызывающий код вместо этого получил ptr + 8, поэтому lengthof (ptr + 8), очевидно, потерпел неудачу с исключением (даже если этого не произошло, он мог иметьпотенциально вернул неправильный размер массива).Являются ли эти 8 байтов накладными расходами или заполнением?Не может быть ранее упомянутого перераспределения, функция работала корректно для многих массивов.Что это такое и как отключить или обойти это, предполагая, что можно использовать специфичные для G ++ вызовы или обман?
Редактировать: Из-за многочисленных способов можно выделить C-В массивах стилей, как правило, нельзя определить длину произвольного массива по его указателю, как предположил Оли Чарльзуорт.Но это возможно для неразрушенных статических массивов (см. Функцию шаблона выше) и массивов, выделенных с помощью пользовательского оператора new [] (size_t, size_t), основанного на идее Бена Фойгта:
#include <gc/gc.h>
#include <gc/gc_cpp.h>
#include <iostream>
#include <map>
typedef std::map<void*, std::pair<size_t, size_t> > ArrayLengthMap;
ArrayLengthMap arrayLengthMap;
inline void* operator new [] (size_t size, size_t count)
throw (std::bad_alloc)
{
void* ptr = GC_malloc(size);
arrayLengthMap[ptr] = std::pair<size_t, size_t>(size, count);
return ptr;
}
inline void operator delete [] (void* ptr)
throw ()
{
ArrayLengthMap::const_iterator it = arrayLengthMap.upper_bound(ptr);
it--;
if( it->first <= ptr and ptr < it->first + it->second.first ){
arrayLengthMap.erase(it->first);
}
GC_free(ptr);
}
inline size_t lengthof (void* ptr)
{
ArrayLengthMap::const_iterator it = arrayLengthMap.upper_bound(ptr);
it--;
if( it->first <= ptr and ptr < it->first + it->second.first ){
return it->second.second;
}
throw std::bad_alloc();
}
int main (int argc, char* argv[])
{
int* v = new (112) int[112];
std::cout << lengthof(v) << std::endl;
}
К сожалению, из-за произвольных накладных расходов и дополнений компилятором, пока нет надежного способа определения длины динамического массива в пользовательском операторе new [] (size_t), если только мы не предполагаем, что отступ меньше размераодин из элементов массива.
Однако существуют и другие виды массивов, для которых вычисление длины может быть возможным, как предложил Бен Фойгт, поэтому должно быть возможно и желательно создать класс-оболочку, который можетпринимает несколько видов массивов (и их длины) в своих конструкторах и неявно или явно конвертируется в другие классы-обертки и типы массивов.Различные времена жизни разных типов массивов могут быть проблемой, но это можно решить с помощью сборки мусора.