offsetof — это то, с чем нужно быть очень осторожным в C++. Это пережиток C. В наши дни мы должны использовать указатели на элементы. Тем не менее, я считаю, что указатели членов на элементы данных переработаны и сломаны - я на самом деле предпочитаю offsetof.
Тем не менее, offsetof полон неприятных сюрпризов.
Во-первых, что касается ваших конкретных вопросов, я подозреваю, что реальная проблема заключается в том, что они адаптировались к традиционному макросу C (который, как я думал, был предусмотрен стандартом C++). Они, вероятно, используют reinterpret_cast для "это C++!" причинам (почему приведение (size_t)?), и char&, а не char*, чтобы попытаться немного упростить выражение.
Приведение к char выглядит избыточным в этой форме, но, вероятно, это не так. (size_t) не эквивалентен reinterpret_cast, и если вы попытаетесь преобразовать указатели на другие типы в целые числа, вы столкнетесь с проблемами. Я не думаю, что компилятор даже позволяет это, но, честно говоря, я страдаю от сбоя памяти ATM.
Тот факт, что char является однобайтовым типом, имеет некоторое значение в традиционной форме, но это может быть единственной причиной того, что приведение снова правильно. Если честно, кажется, я помню приведение к void*, затем char*.
Между прочим, из-за того, что они потрудились использовать специфичные для C++ вещи, им действительно следует использовать std::ptrdiff_t для окончательного приведения.
Впрочем, возвращаясь к неприятным сюрпризам...
VC++ и GCC, вероятно, не будут использовать этот макрос. IIRC, у них есть встроенный компилятор, в зависимости от параметров.
Причина в том, чтобы сделать то, для чего предназначен offsetof, а не то, что делает макрос, который надежен в C, но не в C++. Чтобы понять это, подумайте, что произойдет, если ваша структура использует множественное или виртуальное наследование. В макросе, когда вы разыменовываете нулевой указатель, вы в конечном итоге пытаетесь получить доступ к указателю виртуальной таблицы, которого нет по нулевому адресу, а это означает, что ваше приложение, вероятно, аварийно завершает работу.
По этой причине некоторые компиляторы имеют встроенную функцию, которая просто использует заданный макет структур вместо того, чтобы пытаться вывести тип времени выполнения. Но стандарт С++ не предписывает и даже не предлагает этого - он существует только по причинам совместимости с C. И вам все еще нужно быть осторожным, если вы работаете с иерархиями классов, потому что, как только вы используете множественное или виртуальное наследование, вы не можете предполагать, что макет производного класса соответствует макету базового класса - вы должны убедиться, что смещение допустимо для точного типа времени выполнения, а не только для конкретной базы.
Если вы работаете над библиотекой структур данных, возможно, используя одиночное наследование для узлов, но приложения не могут видеть или использовать ваши узлы напрямую, offsetof работает хорошо. Но, строго говоря, даже тогда есть подвох. Если ваша структура данных находится в шаблоне, узлы могут иметь поля с типами из параметров шаблона (содержащийся тип данных). Если это не POD, технически ваши структуры тоже не POD. И все стандартные требования к offsetof заключаются в том, что он работает для POD. На практике это будет работать - ваш тип не получил виртуальную таблицу или что-то еще только потому, что он не является членом POD, - но у вас нет никаких гарантий.
Если вы знаете точный тип времени выполнения при разыменовании с использованием смещения поля, вы должны быть в порядке даже с множественным и виртуальным наследованием, но ТОЛЬКО если компилятор предоставляет встроенную реализацию offsetof для получения этого смещения в первую очередь. Мой совет - не делайте этого.
Зачем использовать наследование в библиотеке структур данных? Ну, как насчет...
class node_base { ... };
class leaf_node : public node_base { ... };
class branch_node : public node_base { ... };
Поля в node_base автоматически совместно используются (с одинаковым макетом) как в листе, так и в ветви, что позволяет избежать распространенной ошибки в C со случайно разными макетами узлов.
Кстати, смещения можно избежать с такими вещами. Даже если вы используете offsetof для некоторых заданий, node_base по-прежнему может иметь виртуальные методы и, следовательно, виртуальную таблицу, если не требуется разыменовывать переменные-члены. Следовательно, node_base может иметь чисто виртуальные геттеры, сеттеры и другие методы. Как правило, это именно то, что вы должны делать. Использование offsetof (или указателей на элементы) усложняет работу, и его следует использовать в качестве оптимизации только в том случае, если вы знаете, что вам это нужно. Например, если ваша структура данных находится в файле на диске, она вам определенно не нужна - несколько накладных расходов на виртуальные вызовы будут незначительными по сравнению с накладными расходами на доступ к диску, поэтому любые усилия по оптимизации должны быть направлены на минимизацию обращений к диску.
Хммм - немного пошло по касательной. Упс.
person
Steve314
schedule
03.10.2009