В C++11 защищенный означает публичный?
Продолжая что-то выученное в C++, ошибка: базовая функция защищена...
Правила указателя на член в C++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 которая предоставляет дополнительные примеры, мотивирующие изменения, и призывает к тому, чтобы проблема поднималась Evolution Рабочая группа.
Также актуально: ПОЛУЧЕНИЕ 76: Использование и злоупотребление правами доступа
2 ответа
Я видел эту технику, которую я называю "защищенным взломом", упоминавшейся довольно много раз здесь и в других местах. Да, это правильное поведение, и это действительно законный способ обойти защищенный доступ, не прибегая к каким-либо "грязным" взломам.
когда m
является членом класса Base
то проблема с &Derived::m
выражение для создания указателя Derived::*
Тип состоит в том, что указатели членов класса являются контравариантными, а не ковариантными. Это сделало бы результирующие указатели непригодными с Base
объекты. Например, этот код компилируется
struct Base { int m; };
struct Derived : Base {};
int main() {
int Base::*p = &Derived::m; // <- 1
Base b;
b.*p = 42; // <- 2
}
так как &Derived::m
производит int Base::*
значение. Если это произвело int Derived::*
значение, код не сможет скомпилировать в строке 1. И если бы мы попытались исправить это с
int Derived::*p = &Derived::m; // <- 1
он не сможет скомпилироваться в строке 2. Единственный способ заставить его скомпилироваться - выполнить принудительное приведение
b.*static_cast<int Base::*>(p) = 42; // <- 2
что не хорошо.
PS Согласен, это не очень убедительный пример ("просто используйте &Base:m
с самого начала, и проблема решена "). Однако, http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html имеет больше информации, которая проливает свет на то, почему такое решение было сделано первоначально. Они заявляют
Заметки от 04/00 встречи:
Обоснование текущего подхода состоит в том, чтобы позволить как можно шире использовать данное выражение адреса члена. Поскольку указатель на базовый элемент может быть неявно преобразован в указатель на производный член, создание типа выражения указатель на базовый элемент позволяет результату инициализироваться или назначаться либо указателю на базовый элемент. to-base-member или указатель на производный член. Принятие этого предложения позволит только последнее использование.
Главное, что нужно иметь в виду о спецификаторах доступа в C++, это то, что они контролируют, где можно использовать имя. На самом деле он ничего не делает для контроля доступа к объектам. "Доступ к члену" в контексте C++ означает "возможность использовать имя".
Заметим:
class Encapsulator {
protected:
int i;
};
struct Gimme : Encapsulator {
using Encapsulator::i;
};
int main() {
Encapsulator e;
std::cout << e.*&Gimme::i << '\n';
}
Это, e.*&Gimme::i
, разрешено, потому что он вообще не имеет доступа к защищенному члену. Мы получаем доступ к участнику, созданному внутри Gimme
посредством using
декларация. То есть, хотя using
объявление не подразумевает каких-либо дополнительных подобъектов в Gimme
экземпляры, он по-прежнему создает дополнительный член. Члены и подобъекты не одно и то же, и Gimmie::i
является отдельным открытым членом, который может использоваться для доступа к тем же подобъектам, что и защищенный член Encapsulator::i
,
Как только различие между "членом класса" и "подобъектом" понято, должно быть ясно, что на самом деле это не лазейка или непреднамеренный провал контракта, указанного в 11.4 p1.
То, что можно создать доступное имя или иным образом предоставить доступ к иным неименованным объектам, является предполагаемым поведением, даже если оно отличается от некоторых других языков и может вызывать удивление.