Доступ к защищенному члену через указатель члена: это взлом?

Все мы знаем, что члены, указанные в 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;
    }
};

Живая демонстрация на coliru

Почему это возможно, это желаемая функция или сбой где-то в реализации или формулировке Стандарта?


Из комментариев возник еще один вопрос: если Derived::f вызывается с фактическим Base, это неопределенное поведение?

Комментарии не подлежат расширенному обсуждению; этот разговор был переехал в чат.

user3956566 31.03.2018 15:55

@YvetteColomb Это была коллективная попытка найти решение вопроса / улучшить вопрос. Нет возможности их вернуть? В них все еще есть информация, которая может улучшить принятый ответ.

YSC 31.03.2018 17:06

Все они все еще находятся в связанном чате.

user3956566 01.04.2018 01:18

это спасло мне день. Обратите внимание, что метод f может быть статическим, который помогает избежать реального создания объекта Derived.

463035818_is_not_a_number 14.06.2019 17:07
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
52
4
4 596
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

По сути, вы обманываете компилятор, и это должно работать. Я всегда вижу такие вопросы, и люди иногда получают плохие результаты, а иногда это работает, в зависимости от того, как это преобразуется в код ассемблера.

Я помню, как видел случай с ключевым словом const для целого числа, но затем с помощью некоторых уловок парень смог изменить значение и успешно обошел осведомленность компилятора. Результат был: неверное значение для простой математической операции. Причина проста: сборка в x86 действительно делает различие между константами и переменными, потому что некоторые инструкции содержат константы в своем коде операции. Итак, поскольку компилятор верит это константа, он будет рассматривать ее как константу и обрабатывать ее оптимизированным способом с неправильной инструкцией процессора, и, баам, у вас есть ошибка в результирующем числе.

Другими словами: компилятор попытается обеспечить соблюдение всех правил, которые он может применять, но вы, вероятно, в конечном итоге сможете обмануть его, и вы можете получить или не получить неправильные результаты в зависимости от того, что вы пытаетесь сделать, поэтому вам лучше делать такие вещи. только если вы знаете, что делаете.

В вашем случае указатель &Derived::value может быть вычислен из объекта по тому, сколько байтов осталось от начала класса. В основном это то, как компилятор получает к нему доступ, поэтому компилятор:

  1. Не видит проблем с разрешениями, потому что вы получаете доступ к value через derived во время компиляции.
  2. Это можно сделать, потому что вы берете смещение в байтах в объекте, который имеет ту же структуру, что и derived (ну, очевидно, base).

Итак, вы не нарушаете никаких правил. Вы успешно обошли правила компиляции. Вы не должны этого делать именно по причинам, описанным в прикрепленных вами ссылках, поскольку это нарушает инкапсуляцию ООП, но, что ж, если вы знаете, что делаете ...

Я сомневаюсь, что OP спрашивает о практичности

Passer By 29.03.2018 10:38

Доблестное усилие. Но это очень долгий отказ. Потому что я думаю, что OP интересуется, какова формулировка формальный по этому поводу.

StoryTeller - Unslander Monica 29.03.2018 10:38

Я пытался объяснить, почему это работает ... Надеюсь, это поможет.

The Quantum Physicist 29.03.2018 10:39
"можно рассчитать от объекта по количеству байтов от начала класса" - А как насчет множественного наследования?
StoryTeller - Unslander Monica 29.03.2018 10:39

@StoryTeller В каждом отдельном случае результат будет разным. Это зависит от того, как компилятор структурирует объекты в байтах. В конечном итоге компилятор упорядочивает все классы, унаследованные многократно.

The Quantum Physicist 29.03.2018 10:40

Я думаю, проблема в том, что ваш ответ действителен для конкретной архитектуры и / или реализации, но OP хочет его в общем случае, то есть для абстрактной машины C++.

Rakete1111 29.03.2018 11:22

@ Rakete1111 Удивительно, как людям легко опускать ответы, которые им не нравятся, независимо от того, действительны ли они. Я понимаю, что вы имеете в виду под абстрактной машинной точкой, но проблема, о которой говорит OP, является практической проблемой, требующей практического ответа о том, как это работает, и я попытался предоставить это. Я не думаю, что заслуживаю столько нападок со стороны отрицательных избирателей.

The Quantum Physicist 29.03.2018 11:30

Обычно это проще, когда постер упирается в пятки. Главный критерий голосования - «полезность». И хотя ответ с касательными элементами май может быть полезен, это не так. Как и говорит Rakete1111, вы сосредотачиваетесь на аспекте, о котором ОП не заботится.

StoryTeller - Unslander Monica 29.03.2018 11:44

@TheQuantumPhysicist Вопрос, на который вы отвечаете, - "Как это работает?" а не вопрос, который был задан на самом деле: «Почему это работает?». Это тонкое различие, но из вопроса ясно, что у спрашивающего нет никакой путаницы в том, как работают указатели на члены, и он спрашивает «почему» на более глубоком уровне, чем почему он компилируется и работает так, как ожидалось. Вполне возможно, что язык мог указать, что этот вид кода сформирован неправильно, но вместо этого он указывает, что он допустим. В этом и заключается причина, по которой был задан этот вопрос.

Jordan Melo 02.04.2018 17:21

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 в правильной программе вы можете превратить все частное в общедоступное, и это все равно будет правильным

463035818_is_not_a_number 14.06.2019 17:17

Я вижу все такие вещи как инструменты. Безопасность типов, корректность констант, спецификаторы доступа - мы знаем, что можем обойтись без них (выберите свой язык), но мы можем использовать эти инструменты. У каждого решения есть свои преимущества и недостатки. Вы говорите о том, что любое выборочное и преднамеренное отключение всегда будет плохим? Вы когда-нибудь использовали гипс?

Bert Bril 16.06.2019 01:48

Другие вопросы по теме