Сделать взаимозаменяемые типы классов только путем приведения указателя, без необходимости выделять какие-либо новые объекты?

UPDATEЯ действительно ценю "не хочу этого, хочу этого вместо этого" предложения. Они полезны, особенно если они представлены в контекстемотивирующий сценарий, Тем не менее ... независимо от добра / зла, мне стало любопытно быстро и быстро найти"yes that can be done legally in C++11" против"no it is not possible to do something like that".

Я хочу "псевдоним" указатель объекта в качестве другого типа, дляsole purpose добавить некоторые вспомогательные методы. Псевдоним не может добавлять члены данных в базовый класс (на самом деле, чем больше я могу предотвратить это, тем лучше!) Все псевдонимы одинаково применимы к любому объекту этого типа ... это просто полезно, если система типов может подсказка, какой псевдоним, вероятно, является наиболее подходящим.

Не должно быть никакой информации о каком-либо конкретном псевдониме, который когда-либо кодируется в базовом объекте. Следовательно, я чувствую, что вы должны быть в состоянии "обмануть" система типов и просто пусть это будет аннотация ... проверяется во время компиляции, но в конечном итоге не имеет отношения к приведению во время выполнения. Что-то в этом роде:

Node<AccessorFoo>* fooPtr = Node<AccessorFoo>::createViaFactory();
Node<AccessorBar>* barPtr = reinterpret_cast< Node<AccessorBar>* >(fooPtr);

Под капотом заводской метод на самом деле делаетNodeBase класс, а затем с помощью аналогичногоreinterpret_cast вернуть его какNode<AccessorFoo>*.

Самый простой способ избежать этого - создать легкие классы, которые обертывают узлы и передаются по значению. Таким образом, вам не нужно приводить, просто классы Accessor, которые используют дескриптор узла для переноса в своем конструкторе:

AccessorFoo foo (NodeBase::createViaFactory());
AccessorBar bar (foo.getNode());

Но если мне не нужно платить за все это, я не хочу. Это может включать, например, создание специального типа средства доступа для каждого вида обернутых указателей (AccessorFooShared, AccessorFooUnique, AccessorFooWeak и т. Д.). Предпочтительно, чтобы эти типизированные указатели были псевдонимами для одного идентификатора объекта, основанного на указателе, и были бы предпочтительны. хорошая ортогональность.

Итак, вернемся к этому первоначальному вопросу:

Node<AccessorFoo>* fooPtr = Node<AccessorFoo>::createViaFactory();
Node<AccessorBar>* barPtr = reinterpret_cast< Node<AccessorBar>* >(fooPtr);

Похоже, есть какой-то способ сделать это, который может быть уродливым, но не "нарушать правила". В соответствии сISO14882: 2011 (e) 5.2.10-7:

An object pointer can be explicitly converted to an object pointer of a different type.70 When a prvalue v of type "pointer to T1" is converted to the type "pointer to cv T2", the result is static_cast(static_cast(v)) if both T1 and T2 are standard-layout types (3.9) and the alignment requirements of T2 are no stricter than those of T1, or if either type is void. Converting a prvalue of type "pointer to T1" to the type "pointer to T2" (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value. The result of any other such pointer conversion is unspecified.

Бурение в определении& quot; класс стандартной компоновки & quot;, мы нашли:

has no non-static data members of type non-standard-layout-class (or array of such types) or reference, and has no virtual functions (10.3) and no virtual base classes (10.1), and has the same access control (clause 11) for all non-static data members, and has no non-standard-layout base classes, and either has no non-static data member in the most-derived class and at most one base class with non-static data members, or has no base classes with non-static data members, and has no base classes of the same type as the first non-static data member.

Похоже, работа с чем-то вроде этого немного связывает мои руки без виртуальных методов в средствах доступа или узле. Тем не менее, C ++ 11, очевидно, имеетstd::is_standard_layout держать вещи проверенными.

Можно ли это сделать безопасно? Кажется, работает в gcc-4.7, но я хотел бы убедиться, что я не вызываю неопределенное поведение.

 iammilind27 июн. 2012 г., 05:58
Начальный синтаксис должен бытьNode<AccessorFoo>& foo = *new Node<AccessorFoo>;, Я все еще не понимаю вопроса, хотя :)
 ildjarn13 июл. 2012 г., 03:42
& Quot;I want to "alias" an object pointer as another type, for the sole purpose of adding some helper methods.& Quot; Зачем? Что функции-члены дают вам, что бесплатные функции не могут? Если я не пропускаю что-то очевидное (очень возможное), это звучит как немного ошибочная ООП-фанатика ...
 HostileFork27 июн. 2012 г., 06:02
@iammilind Ой, опечатка, исправлено ... спасибо! В принципе, это, вероятно, очень похоже наthis questionЯ просто имею больше контроля над типами и могу переписать все с нуля. Поэтому мне интересно, какие варианты могут открыться для меня в этом конкретном сценарии.
 HostileFork13 июл. 2012 г., 04:08
@ildjarn Может быть? Я жаловался, что SO - это, черт побери, если ты сделаешь объяснение ("What's with all the specifics, what's your actual question?") против проклятого, если ты не объяснишь ("Why would you need that?") среда. :( У меня есть последний класс, который имеет NC-const-операции и N-неконстантные операции, живет в файле с отображением в памяти. Иногда я хотел бы расширить и "ввести" интерфейс этого класса в качестве подсказки, чтобы помочь людям ДОМ - этоbest analogy I've got, sadly

Ответы на вопрос(4)

что строгие правила наложения запрещают то, что вы пытаетесь сделать.

Чтобы уточнить: строгое псевдоним не имеет ничего общего с совместимостью макетов, типами POD или чем-то еще. Это связано с оптимизацией. И с тем, что язык явно запрещает вам делать.

Эта статья довольно хорошо подводит итог:http://dbp-consulting.com/StrictAliasing.pdf

 07 июл. 2012 г., 14:03
@HostileFork: Это не так, потому что компилятор не должен гарантировать, что пока вы выполняетеconst Метод на объекте не меняется под вашими ногами через свой другой тип.
 HostileFork09 июл. 2012 г., 00:47
@MatthieuM. В свете ответа jthill, существует ли такая вещь, как «бросок безопасности»? к основному типу перед каждым доступом, который может обойти проблемы псевдонимов, если строго придерживаться?
 HostileFork06 июл. 2012 г., 05:57
Интересно, спасибо за обновление с PDF, я прочитал и посмотрю, о чем вы говорите. Хотя теперь мне интересно ... если бы все методы класса былиconst, так что члены данных никогда не были изменены в течение жизни ... будетthat быть законным? (И если нет, то почему?)
 HostileFork03 июл. 2012 г., 12:12
+1, так как я нашел несколько полезных тегов (например, «type-punning») и переход от точек к строгому алиасингу. Но большинство из найденных ссылок на строгие псевдонимы, по-видимому, говорят о типах с разным размером или компоновкой, и здесь это только методы, отличающиеся без виртуальной отправки, поскольку все они созданы как один и тот же тип. Кажется, что это вполне нормально для преобразования со знаком в беззнаковые варианты одного и того же типа в строгом псевдониме ... так что я думаю, что этот вопрос о диспетчеризации метода будет зависеть от чего-то вроде этой "стандартной компоновки" определение (если что-нибудь), или, может быть, другое зонтичное правило?
 09 июл. 2012 г., 08:37
@HostileFork: Честно? Я не знаю. И я даже не вижу в этом смысла. Стандарт C ++ чрезвычайно сложен и в нем трудно ориентироваться, и в настоящий момент у меня нет времени, чтобы попытаться проверить все, казалось бы, ошибочные ответы на ваш вопрос. Поэтому я скажу только следующее: чем ближе вы подходите к границам спецификаций, тем больше вероятность того, что вы попадете в ошибку компилятора. Поэтому, как инженер, я советую быть прагматичным и избегать игр с вашим компилятором; правы вы или нет, не имеет значения: когда происходит сбой в рабочем коде,you проиграть, а не авторы компилятора.

A NodeBase class that is stateful, and the true workhorse of the system; a set of stateless Accessor types that provide an interface to NodeBase; and a Node<AccessorT> class which wraps an accessor, presumably providing convenience functions.

Я предполагаю последний бит, потому что, если у вас нет типа-обертки, который делает удобные вещи, то нет никаких причин не делатьAccessor введите ваш верхний уровень, как вы предложили: passAccessorFoo а такжеAccessorBar вокруг по значению. Тот факт, что они не являются одним и тем же объектом, является спорным; если вы думаете о них как о указателях, то они заметят, что&foo != &bar не более интересно, чем иметьNodeBase* p1 = new NodeBase; NodeBase* p2 = p1; и отмечая, что, конечно же,&p1 != &p2.

Если вам действительно нужна оберткаNode<AccessorT> и хочу сделать это стандартным макетом, тогда я бы предложил вам использовать безгражданство вашегоAccessor типы в ваших интересах. Если они представляют собой просто контейнер функциональности без сохранения состояния (которым они должны быть; зачем еще вы могли бы свободно использовать их?), Тогда вы можете сделать что-то вроде этого:

struct AccessorFoo {
    int getValue(NodeBase* n) { return n->getValueFoo(); }
};

struct AccessorBar {
    int getValue(NodeBase* n) { return n->getValueBar(); }
};

template <typename AccessorT>
class Node {
    NodeBase* m_node;

public:
    int getValue() {
        AccessorT accessor;
        return accessor.getValue(m_node);
    }
};

В этом случае вы можете добавить шаблонный оператор преобразования:

template <typename OtherT>
operator Node<OtherT>() {
    return Node<OtherT>(m_node);
}

И теперь у вас есть прямое преобразование значений между любымиNode<AccessorT> типа тебе нравится.

Если вы пойдете немного дальше, вы сделаете все методыAccessor типы статические, и получитеобразец черт.

Раздел стандарта C ++, который вы цитировали, кстати, касается поведенияreinterpret_cast<T*>(p) в случае, когда и исходный тип, и конечный тип являются указателями на объекты стандартной компоновки, и в этом случае стандарт гарантирует, что вы получите тот же указатель, который вы получите от приведения кvoid* а затем до окончательного типа. Вы по-прежнему не можете использовать объект в качестве любого другого типа, кроме того, для которого он был создан, без вызова неопределенного поведения.

 HostileFork07 июл. 2012 г., 13:24
Может показаться, что даже со стандартными типами макетов это запрещено строгими требованиями к псевдонимам (см. @ PaulGroke's).response). В этом случае единственное, что стандарт позволяет вам делать, - это временно удерживать указатель как другой тип, а затем преобразовывать его обратно, но не использовать его тем временем? (Возможно, более конкретно, не вызывать какие-либо неконстантные методы?)
 HostileFork13 июл. 2012 г., 22:56
Поработав над этим вопросом некоторое время, вот что, я полагаю, я собираюсь сделать с:sample library for pattern and test program on Gist, & Quot; Foo & Quot; в данном случае это Node и «Wrapper» это то, что я, вероятно, буду называть NodeRef или что-то подобное. Это все еще кажется чем-то вроде обходного пути для достижения эффекта по сравнению с «типом-наказанием» - но если это устраняет неопределенное поведение и клиентский код выглядит нормально, я думаю, это хорошо ...!
 27 июн. 2012 г., 07:56
@HostileFork Почему бы не инвертировать это, а положитьunique_ptr ВнутриNode<X>? Если вам нужны другие виды интеллектуальных указателей, достаточно легко создать различные вариантыNode<X>особенно если это небольшая обертка вокруг аксессуара. Кроме того, если присмотреться, я не думаю, что уловка стандартной компоновки - это то, что вы думаете. Цитируемый раздел оreinterpret_cast делиться семантикой сstatic_cast в случае стандартных типов размещения. Вы по-прежнему не можете изменять типы, не вызывая неопределенное поведение.
 HostileFork27 июн. 2012 г., 07:41
+1 Да, вы получили суть и методы доступаshould быть статичным ... хотя это будет непросто для авторов Accessor по сравнению с возможностью вызова методов NodeBase, когда они наследовали его. : - Я явно желаю этогоNode<X> типы, которые не должны быть отделены от «рабочей лошадки» Экземпляр NodeBase, потому что я используюunique_ptr<Node<X>> следовать за горячим хозяином владения для узлов. Чтобы продолжать использовать это, я должен добавить еще один уровень отслеживания выделения кучи к этимNode<X> обрабатывает вместо передачи по значению. Таким образом, стандартная схема размещения - это то, что мне наиболее интересно.
 HostileFork27 июн. 2012 г., 08:14
Я не уловил этот бит ... хм ... на самом деле узлы создаются с помощью фабричного метода как NodeBase, а затем отбрасываются в возвращаемом ... это будет здесь законно или это ничего не изменит? Что касается создания легких классов, таких какUniqueNode<X> которые отличаются отNode<X>... звучит возможно, много вещейpossible! Я просто пытаюсь вбить это в четкое разделение проблем, которое не тратит его время на ненужные комбинаторные взрывы классов и является настолько эффективным, насколько это возможно. (Я бы, вероятно, использовал другой язык, если бы не нашел это мотивирующим.) :)
Решение Вопроса

Accessor мертвая распродажа: то, что вы ищете, этоProxy.

Нет никаких причин, чтобы Прокси не передавался по значению.

// Let us imagine that NodeBase is now called Node, since there is no inheritance

class AccessorFoo {
public:
    AccessorFoo(Node& n): node(n) {}

    int bar() const { return node->bar; }

private:
    std::reference_wrapper<Node> node;
};

И тогда вы можете свободно конвертировать из одного средства доступа в другое ... хотяthis smells, Обычно самой целью наличия аксессора является ограничение доступа таким образом, что приведение невольно волевого к другому аксессоруbad, Однако можно поддержать приведение кnarrower сбруя.

 27 июн. 2012 г., 19:31
@HostileFork: НоProxy is not Узел, поскольку он специально предназначен для ограничения доступного интерфейса. Разве это не так?
 HostileFork27 июн. 2012 г., 09:05
+1 Спасибо .. хотя со страниц на этом сайте это было бы больше похоже наFacadeхотя на практике я мог бы хотетьDecorator. "Adapter provides a different interface to its subject. Proxy provides the same interface. Decorator provides an enhanced interface (...) Facade defines a new interface, whereas Adapter reuses an old interface."  Я хочу, чтобы объектно-реляционная модель была компромиссом, чтобы соответствовать моим желаниям (любой выбор будет), но просто не хочуactively illegal C ++. Отсюда вопрос.
 HostileFork27 июн. 2012 г., 19:42
Извините, что я на самом деле не определил, намеревался ли он "ограничить" или "увеличить" интерфейс узла. Более важным моментом является то, что, учитывая понимание клиента, что он можетalways вернуть общий узел, любое «ограничение» будет только "Эй, вы уверены, что хотите сделать это"? шаг, побуждающий вас проверить интерфейс средства доступа, чтобы увидеть, будет ли более подходящей операция более подходящей. Если & quot; ограничение & quot; Оказалось, что именно в этом и заключается смысл, я буду делать фактическое приведение внутри метода-обертки, чтобы имитировать «только производные классы, которые знают об is-a». отношения охраняемого наследства.
 27 июн. 2012 г., 10:20
@HostileFork: имена всегда проблематичны.Facade как правило, больше агрегирование интерфейсов (например,libclang является C-Facade над библиотеками Clang). Идея, чтоProxy Предоставляет тот же интерфейс довольно ограничительный. Из Википедии,Proxy provides a placeholder for another object to control access, reduce cost, and reduce complexity. Ваш вопрос очень похож наcontrol access, в этом случае естественно представить ограниченный интерфейс.
 HostileFork27 июн. 2012 г., 18:07
Ты говоришь"There is no reason for a Proxy not to be passed around by value" что противоречит моему представлению о том, что может быть причина. А именно: если вы хотите эффективную "is-a" отношение тогда, если указатель на Прокси-сервер "were-a" также указатель на узел, тогда вы можете использовать unique_ptr (shared_ptr, weak_ptr и т. д.) с прокси так же, как вы можете использовать его с прямым указателем Node. Это работает, если вы используете наследование C ++ для реализации шаблона, но если ваш объект является реализацией элемента внешней базы данных, это не так. Я спрашиваю, возможно ли восполнить этот пробел в легальном C ++.
struct myPOD {
   int data1;
   // ...
};

struct myPOD_extended1 : myPOD {
   int helper() { (*(myPOD*)this)->data1 = 6; };  // type myPOD referenced here
};
struct myPOD_extended2 : myPOD { 
   int helper() { data1 = 7; };                   // no syntactic ref to myPOD
};
struct myPOD_extended3 : myPOD {
   int helper() { (*(myPOD*)this)->data1 = 8; };  // type myPOD referenced here
};
void doit(myPOD *it)
{
    ((myPOD_extended1*)it)->helper();
    // ((myPOD_extended2*)it)->helper(); // uncomment -> program/compile undefined
    ((myPOD_extended3*)it)->helper();
}

int main(int,char**)
{
    myPOD a; a.data1=5; 
    doit(&a);

    std::cout<< a.data1 << '\n';
    return 0;
}

что это гарантированно работает во всех соответствующих компиляторах C ++ и должно вывести 8. Раскомментируйте отмеченную строку, и все ставки выключены.

Оптимизатор может обрезать поиск действительных ссылок с псевдонимами, проверяя список синтаксических типов, на которые фактически ссылается функция, по списку (в 3.10p10) синтаксических типов ссылок, которые требуются для получения правильных результатов для - и когда фактические ( Тип объекта "динамический" известен, этот список не включает в себя доступ через ссылку на какой-либо производный тип. Таким образом, явные (и действительные) откатыthis вmyPOD* вhelper()ставитmyPOD в списке типов, синтаксически на которые имеются ссылки, и оптимизатор должен рассматривать результирующие ссылки как потенциальные юридические ссылки на (другие имена, псевдонимы) объектаa.

 08 июл. 2012 г., 13:45
Вы можете конвертироватьstructA* к (п даже совершенно не связаны)structB* и обратно, и компилятор должен учитывать эту возможность: преобразование указателя и его передача не означает, что преобразованное значение никогда не может быть использовано для изменения исходного объекта.
 HostileFork09 июл. 2012 г., 00:40
Хорошо ... так что если вы говорите, что приведение к базовому типу перед любыми изменениями предотвращает "псевдонимы", то то, что я хочу сделать, будет законным ...? Потому что мои производные типы никогда не добавляют никаких членов. Я предполагаю, что хитрость заключается в том, чтобы убедиться, что все обращения к элементам данных проходят через то, что вы называете «приведением безопасности». (читает и пишет) вместо того, чтобы пытаться получить доступ напрямую без приведения, которыйwould быть псевдонимом и, следовательно, незаконным?
 HostileFork08 июл. 2012 г., 19:56
Кажется, то, что вы говорите, противоречитstrict aliasing paperЭто означает, что вы можете сделать это только тогда, когда вы выполняете преобразование между фактическим типом, с которым что-то было выделено (например, если вы объявили что-то как myPOD_extended1, а затем приведете его к myPOD и обратно). Это центральный аргумент вопроса, который я задаю, и он действительно звучит так, будто строгий псевдоним позволяет компилятору делать предположения об оптимизации, которые могут подорвать это в других случаях. : - /
 08 июл. 2012 г., 23:26
Как только компилятор видитdoit(&a) нельзя допускать, чтобыa не будет доступен через этот указатель или любую копию. Прогнозы вdoit явно разрешены, см. 5.4p4, иhelper() функции получают копию этого указателя восходящего потока (какthis), поэтому компилятор не может предполагать, что они не ссылаются наmyPOD через эту копию тоже. Я добавил безопасные приведения к вспомогательным функциям; Я думаю, что компилятору пришлось бы приложить все усилия, чтобы неправильно обработать этот случай, но я вижу, что раньше он был не совсем корректным (что в данном обсуждении означает «неправильно»). Я считаю, что сейчас это абсолютно правильно.
 HostileFork08 июл. 2012 г., 08:06
Аргументы псевдонимов, приведенные другими, похоже, позволяют предположить, что компилятор может предположить, что псевдонимы являются другими типами, отличными от того, которым был выделен класс (myPOD в этом случае) не существует. следовательноdata1 может быть изменено в одном из псевдонимов, а затем это изменение не отражается в другом. @MatthieuM предположил, что даже constness не защищает вас, поскольку один из псевдонимов может быть неконстантным. Можете ли вы привести причину, по которой вы думаете, что это будет исключением из правила?

Ваш ответ на вопрос