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

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

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

49

Решение

Тот факт, что член не доступен с помощью доступ к ученикам 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,

28

Другие решения

это взломать?

В аналогичном ключе к использованию reinterpret_cast, это может быть опасно и потенциально может быть источником труднодоступных ошибок. Но он хорошо сформирован, и нет никаких сомнений в том, что он должен работать.

Чтобы прояснить аналогию: поведение reinterpret_cast также точно указано в стандарте и может использоваться без каких-либо UB. Но reinterpret_cast обходит систему типов, и система типов существует не просто так. Точно так же этот указатель на трюк члена правильно сформирован в соответствии со стандартом, но он обходит инкапсуляцию членов, и эта инкапсуляция (как правило) существует по причине (я говорю, как правило, так как я предполагаю, что программист может использовать инкапсуляцию несерьезно).

[Это] глюк где-то в реализации или формулировке Стандарта?

Нет, реализация правильная. Вот как язык был указан для работы.

Функция члена Derived очевидно, может получить доступ &Derived::value, поскольку он является защищенным членом базы.

Результатом этой операции является указатель на член Base, Это может быть применено к ссылке на Base, Права доступа участника не применяются к указателям на участников: это относится только к именам участников.


Из комментариев возник другой вопрос: если Derived :: f вызывается с реальной Base, это неопределенное поведение?

Не UB. Base имеет член.

14

Просто чтобы добавить к ответам и немного увеличить ужас, который я могу прочитать между ваших строк. Если вы рассматриваете спецификаторы доступа как «закон», контролирующий вас, чтобы вы не делали «плохие вещи», я думаю, вы упускаете суть. public, protected, private, const … являются частью системы, которая является огромным плюсом для C ++. Языки без него могут иметь много достоинств, но когда вы создаете большие системы, такие вещи являются реальным преимуществом.

Сказав это: я думаю, это хорошо, что можно обойти почти все сети безопасности, предоставленные вам. Пока вы помните, что «возможно» не означает «хорошо». Вот почему это никогда не должно быть «легким». А в остальном — решать вам. Вы архитектор.

Несколько лет назад я мог просто сделать это (и это все еще может работать в определенных условиях):

#define private public

Очень полезно для «враждебных» внешних заголовочных файлов. Хорошая практика? Как вы думаете? Но иногда ваши возможности ограничены.

Так что да, то, что вы показываете, является нарушением системы. Но эй, что мешает вам получать и распространять публичные ссылки на участника? Если вас не устраивают ужасные проблемы с техническим обслуживанием — почему бы и нет?

-1

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

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

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

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

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

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

-2
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector