Должен ли класс быть стандартным типом макета, чтобы быть уверенным в смещениях памяти его членов?
Допустим, я хочу написать навязчивый список. У меня есть шаблон класса навязчивого списка, который принимает тип и указатель на член для использования в качестве узла. Это выглядит примерно так:
// This needs to be a member of anything the intrusive list is going to store.
class IntrusiveListNode {
// ...
}
// The intrusive list itself
template <class T, IntrusiveListNode T::*Member>
class IntrusiveList {
// ...
};
// This is a type that is going to be stored in an intrusive list.
class IntegerListNode {
public:
IntrusiveListNode node;
private:
int value;
};
// An example of the how the list would be used.
IntrusiveList<IntegerListNode, &IntegerListNode::node> myList;
Каждая вещь, которую вы хотите сохранить в списке, имеет IntrusiveListNode. Чтобы превратить этот IntrusiveListNode обратно в то, что вы можете использовать, например, в IntegerListNode, вы вызываете функцию, которая выполняет некоторую арифметику указателя на узле на основе его смещения в классе, а затем приводит его к соответствующему типу. Кажется, это работает, но я думаю, что это не гарантировано.
Я хотел бы иметь возможность добавить static_assert в мой класс, который во время компиляции проверяет, что тип, который вы используете, является безопасным, но я не уверен, каково будет условие static_assert. Я думаю, что это гарантированно сработает только в том случае, если тип, содержащий IntrusiveListNode, является стандартным классом макета, но я не уверен, так как требования к стандартным типам макета кажутся более строгими, чем мне действительно нужно.
В частности, стандартный тип макета требует, чтобы все элементы имели одинаковый контроль доступа. Что мне нужно, так это просто убедиться, что арифметика указателей будет работать. Это означало бы, что вы не можете использовать это для полиморфного типа, потому что две разные версии структуры могут быть размечены по-разному, но это не должно быть проблемой, если тип имеет некоторое сочетание частных и открытых членов данных, верно? Было бы безопасно, если бы я просто потребовал, чтобы тип был не полиморфным? Или лучше проверить? Или я застрял, делая is_standard_layout
проверять?
1 ответ
Я не могу процитировать стандарт для этого (и я почти уверен, что это undefined-поведение-land), но вы, как правило, можете полагаться на согласованность смещения члена данных относительно указателя на содержащий класс, при условии, что статический тип этого указателя является константой (IntegerListNode* в вашем случае).
Вы можете обнаружить, что смещение изменяется при измерении его относительно указателя на производный тип, но если вы всегда переинтерпретируете сначала в IntegerListNode и только потом статическое / динамическое приведение к производному типу, я бы почти чувствовал себя комфортно с ним:)
Это не значит, что это хорошая идея. Такая арифметика указателей не требуется для реализации навязчивого списка. Если вы просто определяете указатель "следующий" и / или "предыдущий" в IntegerListNode (указывающий на IntegerListNode), вы можете передать указатель на член для этого в IntrusiveList, и вам никогда не понадобятся творческие преобразования:)