Продължаване на нещо научено в 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
е член на базовия клас. Но не виждам нищо в стандарта, което да го прави така, и има много конкретен пример, показващ това.)
Осъзнавам, че някои хора може да са изненадани, че третият коментиран подход (втори тестов случай на ideone) всъщност се проваля. Това е така, защото правилният начин да мислим за защитен не е „моите производни класове имат достъп и никой друг“, а „ако произлизате от мен, вие ще имате достъп до тези наследени променливи, съдържащи се във вашите екземпляри, и никой друг няма, освен ако вие дай го". Например, ако 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.
Също подходящо: GotW 76: Употреби и злоупотреби с права за достъп
private
членове без кастинг - person Andy Prowl   schedule 07.06.2013&A::a
в този контекст. - person Ben Voigt   schedule 07.06.2013reinterpret_cast
-- той позволява подкопаване на правилата по начин, който е лесен за използване, но много очевиден. Мисля, че ако дизайнерите на езика искат да оставят вратата отворена за заобикаляне на проверките за достъп, например за сериализиране на обекти от класове, синтаксисътprivate_access(
member-access-expression
)
би бил много по-добър. Но случаят на използване на сериализация не ме повлиява наистина, тъй като POD обектите могат да бъдат тривиално сериализирани, а тези, които не са POD, изискват помощ от дизайнера на обекти. - person Ben Voigt   schedule 07.06.2013