Продолжение чего-то в Ошибка С++: защищена базовая функция...
Правила с указателем на элемент С++ 11 эффективно разбивают ключевое слово protected
любого значения, так как защищенные члены могут быть доступны в несвязанных классах без каких-либо злых/небезопасных бросков.
В частности:
class Encapsulator
{
protected:
int i;
public:
Encapsulator(int v) : i(v) {}
};
Encapsulator f(int x) { return x + 2; }
#include <iostream>
int main(void)
{
Encapsulator e = f(7);
// forbidden: std::cout << e.i << std::endl; because i is protected
// forbidden: int Encapsulator::*pi = &Encapsulator::i; because i is protected
// forbidden: struct Gimme : Encapsulator { static int read(Encapsulator& o) { return o.i; } };
// loophole:
struct Gimme : Encapsulator { static int Encapsulator::* it() { return &Gimme::i; } };
int Encapsulator::*pi = Gimme::it();
std::cout << e.*pi << std::endl;
}
Это действительно соответствие по стандарту?
(я считаю это дефектом и утверждаю, что тип &Gimme::i
действительно должен быть int Gimme::*
, хотя i
является членом базового класса. Но я ничего не вижу в стандарте, который делает это поэтому, и там есть очень конкретный пример, показывающий это.)
Я понимаю, что некоторые люди могут быть удивлены тем, что третий прокомментированный подход (второй случай проверки идеона) действительно терпит неудачу. Это потому, что правильный способ думать о защите не "мои производные классы имеют доступ и никто другой", но "если вы проистекаете из меня, у вас будет доступ к этим унаследованным переменным, содержащимся в ваших экземплярах, и никто другой не будет, если только вы дайте его". Например, если Button
наследует Control
, тогда защищенные члены Control
внутри экземпляра Button
доступны только для Control
и Button
, и (если Button
не запрещает это) фактический динамический тип экземпляра и любые промежуточные базы.
Эта лазейка подрывает этот контракт и полностью противоречит духу правила 11.4p1:
Дополнительная проверка доступа за пределами тех, которые описаны ранее в разделе 11, применяется, когда нестатический член данных или нестатическая функция-член является защищенным членом его класса именования. Как описано выше, доступ к защищенному члену предоставляется, поскольку ссылка встречается у друга или члена некоторого класса
C
. Если доступ заключается в формировании указателя на член (5.3.1), спецификатор вложенного имени должен обозначатьC
или класс, полученный изC
. Все другие обращения включают (возможно неявное) выражение объекта. В этом случае класс выражения объекта должен бытьC
или класс, полученный изC
.
Спасибо AndreyT за ссылку http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#203, в которой приводятся дополнительные примеры, мотивирующие изменение, и призывает к тому, чтобы проблема была поднята Рабочая группа по эволюции.
Также уместно: GotW 76: Использование и злоупотребления правами доступа