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


Также актуально: ПОЛУЧИЛО 76: Использование и злоупотребление правами доступа

13

Решение

Я видел эту технику, которую я называю «защищенным взломом», упоминавшейся довольно много раз здесь и в других местах. Да, это правильное поведение, и это действительно законный способ обойти защищенный доступ, не прибегая к каким-либо «грязным» взломам.

когда 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

что не хорошо.

Постскриптум Я согласен, это не очень убедительный пример («просто используйте &Base:m с самого начала и проблема решена «). Однако http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#203 имеет больше информации, которая проливает свет на то, почему такое решение было принято изначально. Они заявляют

Заметки от 04/00 встречи:

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

11

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

Главное, что нужно иметь в виду о спецификаторах доступа в 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.

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

5

По вопросам рекламы [email protected]