А как это можно прибавлять что-то там к p, если это экземпляр класса без перегруженного оператора? Если же это должен быть указатель, тогда мне непонятно, зачем всё это.
С одной стороны, если вы уверены, что записи будут храниться сплошняком в памяти, а не в виде списка или чего-то ещё, то можно и в лоб сделать, безо всяких доп. классов:
Record records[REC_NUM];
proc2(records);
...
void proc2(Record* records) {
void* ptr = static_cast<void*>(records);
ptr = static_cast<char*>(ptr) + offsetof(Record, field);
for (i = 0; i < REC_NUM; ++i) {
long* field_ptr = (long*) rec_ptr;
cout << *field_ptr << endl;
++rec_ptr;
}
}
Если же НЕ сплошняком или просто хочется свою обёртку сделать, тогда вот так как-то можно:
class RecordWrapper : public Record {
public:
long* field() {
void* ptr = static_cast<void*>(this);
ptr = static_cast<char*>(ptr) + offsetof(Record, field);
return static_cast<long*>(ptr);
}
};
void proc3(Record* records) {
RecordWrapper* wrappers = static_cast<RecordWrapper*>(records);
for (int i = 0; i < REC_NUM; ++i) {
cout << *wrappers[i].field() << endl;
}
}
Тут только важно учитывать, что в RecordWrapper нельзя новые поля добавлять...
Но смысла осбого я в этом не вижу... разве что для экспериментов.
Здравствуйте, TailWind, Вы писали:
TW>Ой, Конечно же d += Size;
А, ну да Тогда ясно. Но вот обёртка Ptr -- не очень понятна. В общем, вместо того, чтобы вручную прибавлять sizeof(Record), можно указатель сделать типа Record* и затем делать инкремент этого указателя. Не могу сказать, насколько это очевидно, но зато этим займётся компилятор.
Здравствуйте, TailWind, Вы писали:
TW>Загвоздка как раз в том, что Record не известен в Proc, известен только её размер
TW>Можно конечно вот так извратиться, но это UB имхо:
TW>
Вроде выглядит поаккуратнее, но та же проблема с выравниванием остаётся. Даже размер структуры может стать больше. Скорее всего, нужно использовать pragmы, чтобы совпасть с теми параметрами, которые имеются в исходной структуре.
Видимо, будем ждать тех, кто с этим сталкивался на практике.
Здравствуйте, TailWind, Вы писали:
TW>Посоветуйте, как улучшить / ускорить / сделать нагляднее TW>Задача: знаем смещение поля в структуре, нужно организовать быстрый и удобный доступ
Как-то так:
//Сначала делаем универсальный итератор для навигации
//по определенному полю в массиве однотипных структурtypedef unsigned char UCHAR;
typedef unsigned long ULONG;
template<typename FieldT>
class FieldIterator
{
public:
template<typename StructT>
FieldIterator(StructT* sructures, FieldT StructT::*field)
: _pointer_to_field(&(sructures->*field))
, _increment_in_bytes(sizeof(StructT))
{ }
FieldT& operator*() const { return *_pointer_to_field; }
FieldIterator& operator++() { reinterpret_cast<UCHAR*&>(_pointer_to_field) += _increment_in_bytes; return *this; }
FieldIterator& operator++(int) const { FieldIterator it(*this); ++*this; return it; }
private:
FieldT* _pointer_to_field;
ULONG _increment_in_bytes;
};
//И теперь применение этого итератора#include <iostream>
void Proc(FieldIterator<ULONG> iterator, ULONG count)
{
for(ULONG i = 0; i < count; ++i)
{
*iterator = 100 + i * 10;
std::cout << *iterator << ", ";
++iterator;
}
std::cout << std::endl;
}
struct Record
{
int i;
ULONG field;
bool b;
};
int main()
{
Record Records[1000];
Proc(FieldIterator<ULONG>(Records, &Record::field), 1000);
}
K>А насколько безопасно/порядочно/этично использовать char* для адресной арифметики с шагом в ровно один байт?
Гарантировано стандартом:
5.3.3/1
The sizeof operator yields the number of bytes in the object representation of its operand. The operand is either an expression, which is not evaluated, or a parenthesized type-id. The sizeof operator shall not be applied to an expression that has function or incomplete type, or to an enumeration type before all its enumerators have been declared, or to the parenthesized name of such types, or to an lvalue that designates a bit-field. sizeof(char), sizeof(signed char) and sizeof(unsigned char) are 1; the result of sizeof applied to any other fundamental type (3.9.1) is implementation-defined. [Note: in particular, sizeof(bool) and sizeof(wchar_t) are implementation-defined.69) ] [Note: See 1.7 for the definition of byte and 3.9 for the definition of object representation. ]
Непонятно для чего в этом случае массв Records вообще нужен. Посмотри, ты его создал, но никак не использовал — ни при конструировании итератора, ни в процедуру не передал
R>Непонятно для чего в этом случае массв Records вообще нужен. Посмотри, ты его создал, но никак не использовал — ни при конструировании итератора, ни в процедуру не передал
Извините
Плохо формулирую, так ещё и ошибки леплю
int main()
{
FieldIterator<ULONG> Iter(&Record::field);
Record Records[1000];
Proc(Records, Iter, 1000);
}
void Proc(UCHAR*, Iter I, ULONG Size) {...}
K>>А насколько безопасно/порядочно/этично использовать char* для адресной арифметики с шагом в ровно один байт?
R>Гарантировано стандартом: R>
R>5.3.3/1
R>The sizeof operator yields the number of bytes in the object representation of its operand. The operand is either an expression, which is not evaluated, or a parenthesized type-id. The sizeof operator shall not be applied to an expression that has function or incomplete type, or to an enumeration type before all its enumerators have been declared, or to the parenthesized name of such types, or to an lvalue that designates a bit-field. sizeof(char), sizeof(signed char) and sizeof(unsigned char) are 1; the result of sizeof applied to any other fundamental type (3.9.1) is implementation-defined. [Note: in particular, sizeof(bool) and sizeof(wchar_t) are implementation-defined.69) ] [Note: See 1.7 for the definition of byte and 3.9 for the definition of object representation. ]
Так-то оно так, только вот в данном случае мы через lvalue типа unsigned char* пытаемся оперировать указателем, который в действительности может не являться указателем на unsigned сhar. И тут следует обратить внимание на 3.10/15:
If a program attempts to access the stored value of an object through an lvalue of other than one of the following
types the behavior is undefined [Footnote 48: The intent of this list is to specify those circumstances in which an object may or may not be aliased.]
— the dynamic type of the object,
— a cv-qualified version of the dynamic type of the object,
— a type that is the signed or unsigned type corresponding to the dynamic type of the object,
— a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
— an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union),
— a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
— a char or unsigned char type.
If a program attempts to access the stored value of an object through an lvalue of other than one of the following
types the behavior is undefined [Footnote 48: The intent of this list is to specify those circumstances in which an object may or may not be aliased.]
— the dynamic type of the object,
— a cv-qualified version of the dynamic type of the object,
— a type that is the signed or unsigned type corresponding to the dynamic type of the object,
— a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
— an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union),
— a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
— a char or unsigned char type.
Т.е. из последней строки как бы следует, что указатель как раз и надо приводить через reinterpret_cast<char*>, чтобы добиться побайтовой адресной арифметики? Т.е. все реальные пацаны так и делают? Не приходилось мне драйвера писать, потому и спрашиваю.
TW>int main()
TW>{
TW> FieldIterator<ULONG> Iter(&Record::field);
TW> Record Records[1000];
TW> Proc(Records, Iter, 1000);
TW>}
TW>void Proc(UCHAR*, Iter I, ULONG Size) {...}
TW>
Ох, не нравится мне эта заливная рыба. Тут явно требуется редизайн. Только рати того, чтоб не останавливаться на полпути доведу уже начатое до какой-то точки:
template<typename FieldT>
class FieldIterator
{
public:
FieldIterator(FieldT* pointer_to_field, int increment_in_bytes)
: _pointer_to_field(pointer_to_field)
, _increment_in_bytes(increment_in_bytes)
{ }
FieldT& operator*() const { return *_pointer_to_field; }
FieldIterator& operator++()
{
reinterpret_cast<char*&>(_pointer_to_field) += _increment_in_bytes;
return *this;
}
private:
FieldT* _pointer_to_field;
int _increment_in_bytes;
};
template<typename FieldT, typename StructT>
FieldIterator<FieldT> field_iterator(StructT* item, int offset)
{
char* field_bytes = reinterpret_cast<char*>(item) + offset;
FieldT* poiter_to_field = reinterpret_cast<FieldT*>(field_bytes);
return FieldIterator<FieldT>(poiter_to_field, sizeof(StructT));
}
template<typename StructT, typename FieldT>
int field_offset(FieldT StructT::*field)
{
return reinterpret_cast<int>(&(static_cast<StructT*>(0)->*field));
}
//Использование#include <iostream>
typedef unsigned long ULONG;
struct Record
{
int i;
ULONG field;
bool b;
};
void Proc(FieldIterator<ULONG> iterator, int count)
{
for(int i = 0; i < count; ++i)
{
*iterator = i;
std::cout << *iterator << ", ";
++iterator;
}
std::cout << std::endl;
}
int main()
{
int offset = field_offset(&Record::field);
Record records[100];
Proc(field_iterator<ULONG>(records, offset), 100);
}
Здравствуйте, Masterkent, Вы писали:
M>Так-то оно так, только вот в данном случае мы через lvalue типа unsigned char* пытаемся оперировать указателем, который в действительности может не являться указателем на unsigned сhar. И тут следует обратить внимание на 3.10/15:
Согласен, косяк. Можно обойти, если в итераторе инкапсулировать указатель типа unsigned char*, и реинтерпретировать в операторе разыменования, а не в инкременте:
Kirikaza:
K>Т.е. из последней строки как бы следует, что указатель как раз и надо приводить через reinterpret_cast<char*>, чтобы добиться побайтовой адресной арифметики? Т.е. все реальные пацаны так и делают? Не приходилось мне драйвера писать, потому и спрашиваю.
Речь, скорее, о том, что побайтовое чтение через lvalue типа char или unsigned char не приводит к undefined behavior.
Что касается преобразований типов, то вот строгие формальные гарантии стандарта C++03, которые я вижу:
(1) Каждый объект некого типа T представлен последовательностью объектов типа unsigned char в количестве sizeof(T) штук (см. 3.9/4). Объект типа unsigned char представлен самим собой. (2) Стандартное преобразование указателя на объект X к типу void* даёт нам указатель, указывающий на начало области памяти, где хранится объект X (см. 4.10/2), т.е. на самый первый объект типа unsigned char в представлении объекта X (см. (1)). (3) При direct-инициализации указателя типа void* указателем типа T*, где T — объектный тип, выполняется стандартное преобразование — Pointer conversion (см. 8.5/14). (4) static_cast<void *>(p), где p — указатель на объект, выполняет то же преобразование, которое имеет место при direct-initialization (см. 8.5/12, 5.2.9). (5) сast-выражение (void *)p, где p — указатель типа cv T* (T — объектный тип), выполняет то же преобразование, что и const_cast<void *>(static_cast<cv void *>(p));
cast-выражение (T *)p, где T — объектный тип, а p — указатель типа void*, выполняет то же преобразование, что и static_cast<T *>(p) (см. 5.4/5). (6) Значение указателя, полученного преобразованием const_cast, совпадает со значением исходного указателя (см. 5.2.11/3). (7) Преобразование указателя p на объект X к типу void* посредством cast-expression вида (void *)p даёт нам указатель, указывающий на самый первый объект типа unsigned char в представлении объекта X (следствие из (2), (3), (4), (5) и (6)). (8) Если указатель p типа unsigned char* указывает на самый первый объект типа unsigned char в представлении некоего объекта X типа T, то указатель (void *)p будет указывать на тот же самый объект типа unsigned char (следствие из (1) и (7)). (9) Если указатель p типа unsigned char* указывает на самый первый объект типа unsigned char в представлении некоего объекта X типа T, а указатель q типа T* указывает на X, то (void *)p == (void *)q (следствие из (7) и (8)). (10) Если указатель p типа cv T* указывает на объект, то static_cast<cv T *>(static_cast<cv void *>(p)) == p (см. 5.2.9/10). (11) Если указатель p типа T* указывает на объект, то (T *)(void *)p == p (следствие из (5), (6) и (10)). (12) Если указатель p типа T* указывает на объект X, то указатель (unsigned char *)(void *)p указывает на самый первый объект типа unsigned char в представлении объекта X (следствие из (9) и (11)). (13) Если указатель p типа unsigned char* указывает на самый первый объект типа unsigned char в представлении некоего объекта X типа T, то указатель (T *)(void *)p указывает на объект X (следствие из (9) и (11)).
В соответствии с этими гарантиями, следующий код всегда должен работать успешно:
#include <cassert>
#include <cstddef>
template <class T, class CVSample>
struct AddCVOf
{ typedef T type; };
template <class T, class U>
struct AddCVOf<T, U const>
{ typedef T const type; };
template <class T, class U>
struct AddCVOf<T, U volatile>
{ typedef T volatile type; };
template <class T, class U>
struct AddCVOf<T, U const volatile>
{ typedef T const volatile type; };
template <class FieldType, class StructType>
typename AddCVOf<FieldType, StructType>::type &
get_field(StructType &x, std::size_t offset)
{
typedef typename AddCVOf<FieldType, StructType>::type Result;
return *(Result *)(void *)(((unsigned char *)(void *)&x) + offset);
}
struct A
{
double d;
int i;
};
int main()
{
A a;
A const &r = a;
std::size_t offset = offsetof(A, i);
get_field<int>(a, offset) = 123;
assert(get_field<int>(r, offset) == 123);
assert(a.i == 123);
}