Все мы знаем, что члены, указанные в protected из базового класса, могут быть доступны только из собственного экземпляра производного класса. Это функция из Стандарта, и она неоднократно обсуждалась в Stack Overflow:
Но кажется возможным обойти это ограничение с помощью указателей на элементы, как пользователь chtz показал мне:
struct Base { protected: int value; };
struct Derived : Base
{
void f(Base const& other)
{
//int n = other.value; // error: 'int Base::value' is protected within this context
int n = other.*(&Derived::value); // ok??? why?
(void) n;
}
};
Почему это возможно, это желаемая функция или сбой где-то в реализации или формулировке Стандарта?
Из комментариев возник еще один вопрос: если Derived::f вызывается с фактическим Base, это неопределенное поведение?
@YvetteColomb Это была коллективная попытка найти решение вопроса / улучшить вопрос. Нет возможности их вернуть? В них все еще есть информация, которая может улучшить принятый ответ.
Все они все еще находятся в связанном чате.
это спасло мне день. Обратите внимание, что метод f может быть статическим, который помогает избежать реального создания объекта Derived.





По сути, вы обманываете компилятор, и это должно работать. Я всегда вижу такие вопросы, и люди иногда получают плохие результаты, а иногда это работает, в зависимости от того, как это преобразуется в код ассемблера.
Я помню, как видел случай с ключевым словом const для целого числа, но затем с помощью некоторых уловок парень смог изменить значение и успешно обошел осведомленность компилятора. Результат был: неверное значение для простой математической операции. Причина проста: сборка в x86 действительно делает различие между константами и переменными, потому что некоторые инструкции содержат константы в своем коде операции. Итак, поскольку компилятор верит это константа, он будет рассматривать ее как константу и обрабатывать ее оптимизированным способом с неправильной инструкцией процессора, и, баам, у вас есть ошибка в результирующем числе.
Другими словами: компилятор попытается обеспечить соблюдение всех правил, которые он может применять, но вы, вероятно, в конечном итоге сможете обмануть его, и вы можете получить или не получить неправильные результаты в зависимости от того, что вы пытаетесь сделать, поэтому вам лучше делать такие вещи. только если вы знаете, что делаете.
В вашем случае указатель &Derived::value может быть вычислен из объекта по тому, сколько байтов осталось от начала класса. В основном это то, как компилятор получает к нему доступ, поэтому компилятор:
value через derived во время компиляции.derived (ну, очевидно, base).Итак, вы не нарушаете никаких правил. Вы успешно обошли правила компиляции. Вы не должны этого делать именно по причинам, описанным в прикрепленных вами ссылках, поскольку это нарушает инкапсуляцию ООП, но, что ж, если вы знаете, что делаете ...
Я сомневаюсь, что OP спрашивает о практичности
Доблестное усилие. Но это очень долгий отказ. Потому что я думаю, что OP интересуется, какова формулировка формальный по этому поводу.
Я пытался объяснить, почему это работает ... Надеюсь, это поможет.
@StoryTeller В каждом отдельном случае результат будет разным. Это зависит от того, как компилятор структурирует объекты в байтах. В конечном итоге компилятор упорядочивает все классы, унаследованные многократно.
Я думаю, проблема в том, что ваш ответ действителен для конкретной архитектуры и / или реализации, но OP хочет его в общем случае, то есть для абстрактной машины C++.
@ Rakete1111 Удивительно, как людям легко опускать ответы, которые им не нравятся, независимо от того, действительны ли они. Я понимаю, что вы имеете в виду под абстрактной машинной точкой, но проблема, о которой говорит OP, является практической проблемой, требующей практического ответа о том, как это работает, и я попытался предоставить это. Я не думаю, что заслуживаю столько нападок со стороны отрицательных избирателей.
Обычно это проще, когда постер упирается в пятки. Главный критерий голосования - «полезность». И хотя ответ с касательными элементами май может быть полезен, это не так. Как и говорит Rakete1111, вы сосредотачиваетесь на аспекте, о котором ОП не заботится.
@TheQuantumPhysicist Вопрос, на который вы отвечаете, - "Как это работает?" а не вопрос, который был задан на самом деле: «Почему это работает?». Это тонкое различие, но из вопроса ясно, что у спрашивающего нет никакой путаницы в том, как работают указатели на члены, и он спрашивает «почему» на более глубоком уровне, чем почему он компилируется и работает так, как ожидалось. Вполне возможно, что язык мог указать, что этот вид кода сформирован неправильно, но вместо этого он указывает, что он допустим. В этом и заключается причина, по которой был задан этот вопрос.
is it a hack?
Подобно использованию reinterpret_cast, это может быть опасно и потенциально может стать источником трудных для поиска ошибок. Но он хорошо сформирован, и нет никаких сомнений в том, что он должен работать.
Чтобы прояснить аналогию: поведение reinterpret_cast также точно указано в стандарте и может использоваться без каких-либо UB. Но reinterpret_cast обходит систему типов, и система типов существует не зря. Точно так же этот трюк с указателем на член хорошо сформирован в соответствии со стандартом, но он обходит инкапсуляцию членов, и эта инкапсуляция (обычно) существует по определенной причине (я говорю обычно, поскольку я полагаю, что программист может использовать инкапсуляцию легкомысленно).
[Is it] a glitch somewhere in the implementation or the wording of the Standard?
Нет, реализация правильная. Вот как был указан язык для работы.
Функция-член Derived, очевидно, может получить доступ к &Derived::value, поскольку он является защищенным членом базы.
Результатом этой операции является указатель на член Base. Это может быть применено к ссылке на Base. Привилегии доступа к членам не применяются к указателям на члены: они применяются только к именам членов.
From comments emerged another question: if Derived::f is called with an actual Base, is it undefined behaviour?
Не UB. Base имеет член.
Тот факт, что член недоступен с использованием доступ для членов классаexpr.ref (aclass.amember) из-за контроль доступа[class.access], не делает этот член недоступным с использованием других выражений.
Выражение &Derived::value(чей тип - int Base::*) полностью соответствует стандарту и обозначает член value в Base. Тогда выражение a_base.*p, где p - указатель на член Base, а a_base - экземпляр Base, также является стандартное соответствие.
Таким образом, любой совместимый со стандартом компилятор должен определять поведение выражения other.*(&Derived::value);: обращаться к члену value в other.
Просто чтобы добавить к ответам и немного увеличить ужас, который я могу прочитать между вашими строками. Если вы видите спецификаторы доступа как «закон», следящие за тем, чтобы вы не совершали «плохих вещей», я думаю, вы упускаете суть. public, protected, private, const ... все они являются частью системы, что является огромным плюсом для C++. Языки без него могут иметь много достоинств, но когда вы создаете большие системы, такие вещи становятся реальным преимуществом.
Сказав это: я думаю, что это хорошо, что можно обойти почти все предоставленные вам сети безопасности. Если вы помните, что «возможно» не означает «хорошо». Вот почему это никогда не должно быть «легким». А в остальном - решать только вам. Вы архитектор.
Несколько лет назад я мог просто сделать это (и это все еще может работать в определенных средах):
#define private public
Очень полезно для «враждебных» внешних файлов заголовков. Хорошая практика? Как вы думаете? Но иногда ваши возможности ограничены.
Так что да, то, что вы показываете, - это своего рода брешь в системе. Но послушайте, что удерживает вас от получения и раздачи публичных ссылок на участника? Если вас заводят ужасные проблемы с обслуживанием - почему бы и нет?
я нахожу первый абзац довольно запутанным. что еще такое спецификаторы доступа, если не способ «уберечь вас от« плохих вещей »»? Afaik в правильной программе вы можете превратить все частное в общедоступное, и это все равно будет правильным
Я вижу все такие вещи как инструменты. Безопасность типов, корректность констант, спецификаторы доступа - мы знаем, что можем обойтись без них (выберите свой язык), но мы можем использовать эти инструменты. У каждого решения есть свои преимущества и недостатки. Вы говорите о том, что любое выборочное и преднамеренное отключение всегда будет плохим? Вы когда-нибудь использовали гипс?
Комментарии не подлежат расширенному обсуждению; этот разговор был переехал в чат.