Вокруг public, private и protected

By valker

Тема довольно избита, однако, я всё-таки решил начать с неё. Думаю, новичкам будет полезно.

Итак, public, private и protected – это модификаторы доступа, а не видимости, как ошибочно думают некоторые. Private члены видны снаружи класса, но не доступны.

Теперь кратко, кому какой доступ они предоставляют.

  • Public – доступ открыт всем, кто видит определение данного класса.
  • Private – доступ открыт самому классу (т.е. функциям-членам данного класса) и друзьям (friend) данного класса, как функциям, так и классам.
  • Protected – доступ открыт классам, производным от данного.

Далее приведены примеры доступа с указанием какие поля в каких местах программы доступны.

1 class some {

2   friend void f(some&);

3 public:

4   int a_;

5 protected:

6   int b_;

7 private:

8   int c_;

9 };

10

11 void f(some& obj) {

12   obj.a_ = 0; // ok

13   obj.b_ = 0; // ok

14   obj.c_ = 0; // ok

15 }

16

17 void g(some& obj) {

18   obj.a_ = 0; // ok

19   obj.b_ = 0; // CT error

20   obj.c_ = 0; // CT error

21 }

22

23 class derived : public some {

24   derived() {

25     a_ = 0; // ok

26     b_ = 0; // ok

27     c_ = 0; // CT error

28   }

29 };

В C++ существует public-наследование, private-наследование и protected-наследование. В зависимости от того, какой тип используется, изменяется доступ к членам базового класса для клиентов производного. В таблице сведена информация об этом изменении.

Исходный модификатор доступа
public private protected
public-наследование public private protected
private-наследование private private private
protected-наследование protected private protected

Следует добавить, что производный класс может изменить модификатор доступа с protected на public, разместив using объявление в соответствующей секции класса:

1 class some {

2 public:

3   int a;

4 protected:

5   int b;

6 private:

7   int c;

8 };

9

10 class derived : public some {

11 public:

12   using some::b;

13 };

14

15 void f(derived& obj) {

16   obj.b = 0; // ok

17 }

Напоследок приведу несколько приёмов, с помощью которых можно «достучаться» до закрытых функций или данных. Допустим, у нас есть класс some и нам нужно обнулить закрытую переменную c:

  • Модифицировать определение класса, добавив друга (функцию или класс)

    1 class some {

    2   friend class some_friend;

    3 public:

    4   int a;

    5 protected:

    6   int b;

    7 private:

    8   int c;

    9 };

    10

    11 class some_friend {

    12 public:

    13   static void hack(some& obj) {

    14     obj.c = 0;

    15   }

    16 };

  • Воспользоваться препроцессором:

    1 #define private public

    2

    3 class some {

    4 public:

    5   int a;

    6 protected:

    7   int b;

    8 private:

    9   int c;

    10 };

    11

    12 void hack(some& obj) {

    13   obj.c = 0;

    14 }

  • Создать класс с таким же расположением в памяти и воспользоваться reinterpret_cast для преобразования указателей:

    1 class some {

    2 public:

    3   int a;

    4 protected:

    5   int b;

    6 private:

    7   int c;

    8 };

    9

    10 class hack_some {

    11 public:

    12   int a;

    13   int b;

    14   int c;

    15 };

    16

    17 void h(some& obj) {

    18   reinterpret_cast<hack_some*>(&obj)->c = 0;

    19 }

  • Если у «взламываемого» класса есть шаблонная функция, можно её специализировать своим типом:

    1 class some {

    2 public:

    3   int a;

    4   template<class T> void func(void) {

    5     a = b + c;

    6   }

    7 protected:

    8   int b;

    9 private:

    10   int c;

    11 };

    12

    13 class hack_template_param{};

    14

    15 template<>

    16 void some::func<hack_template_param>(void) {

    17   c = 0;

    18 }

    19

    20 void hack(void) {

    21   some o;

    22   o.func<hack_template_param>();

    23 };

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

На сегодня всё.

Метки: , , ,

21 коммент. на “Вокруг public, private и protected”

  1. Сергей:

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

  2. valker:

    Сергей, спасибо, поправил.

  3. meowth:

    Фигассе отжиг, афтор :) Новичкам очень полезно знать, как убить и без того кривой ООП в с++, это вы правильно подметили.
    За идею корежить private поля убить-убить-убить РЖАВОЙ СЕКИРОЙ УЖОСА =).

    С наилучшими, Meowth.

  4. valker:

    meowth, :) Улыбнуло

  5. Alno:

    Взломы для доступа к private стоит все-таки убрать, наверное, действительно.

    И еще, тяжело код читать, стоит подправить стили. Firefox 3b5

  6. valker:

    Alno, у меня Opera, всё хорошо отображает. Может, в FF3 проблема? А насчёт убрать, не согласен. Зачем закрывать доступ к информации? Я же не пропагандирую постоянно писать в таком стили, но может сложиться ситуация, когда иного пути просто не будет.

  7. Сергей:

    Все, вспомнил;) Меня долго мучало ощущение дежавю – где-то я такое уже читал. И вот я вспомнил – это примеры с книги Herb Sutter «Exceptional C++ Style». Я прав?

  8. valker:

    Сергей, действительно так. Думаю, что и в других источниках (и в книгах и в сети) можно найти эту информацию.

  9. Сергей:

    Мощная книга, кстати. Взламывает наивные представления о возможности языка С++

  10. valker:

    Сергей, для меня такими «взломщиками» стали Элджер и Александреску :)

  11. Torvin:

    аффтар аццкий хакир
    самая смешная статья по С++ которую я когда-либо читал

  12. valker:

    Torvin, а что конкретно насмешило?

  13. Torvin:

    в С++ нет средств для защиты кода. это логично, поскольку С/С++ – никсовые детища. чтобы распространять даже скомпиленную либу, нужно предоставить заголовочный файл с определением всех классов, который с легкостью подвергается изменению (которое в любом случае необходимы для способов 1-2). таким образом если нужно добраться до закрытого члена даже класса из сторонней библиотеки – сделать это не представляет сложности.
    а все потому, что модификаторы доступа служат не для «защиты» каких-то «секретных» частей кода, а _исключительно_ для защиты от программистких ошибок. т.е. для инкапсуляции. в тех же целях добавлен модификатор const. автор похоже этого не понимает…

    единственную практическую ценность представляет лишь способ 3 с reinterpret_cast который пригодится в случае с чем-то вроде COM. и к С++ напрямую не имеет никакого отношения.

    P.S.
    #define private public жжот. надеюсь вы это не в серьез

  14. valker:

    Torvin, спасибо за ответ. Автор понимает для чего служат модификаторы доступа. Тем не менее, в результате совокупного действия разных факторов у вас на руках может оказаться библиотека без исходных кодов, с которой придётся проделать операцию описанную в этой заметке. Дай бог, чтобы этого с вами не случилось :)

  15. Torvin:

    вот я и говорю что не бывает C++ библиотеки без заголовочного файла. в котором можно любые private-мемберы исправить на public.
    где вы такую библиотеку видели???

  16. valker:

    Torvin, исправить-то можно, только потом можно поиметь проблемы с линкером, если он имена для public и private членов по-разному преобразует.

  17. valker:

    Простая защита от таких вещей – идиома PImpl.

  18. Torvin:

    ну те же самые проблемы будут и в случае с #define

  19. valker:

    Torvin, по-моему, это очевидно :)

  20. Victor:

    Огромное тебе спасибо за пример с reinterpret_cast.

  21. gajets:

    Кста, постите тексты в социалки. Больше народу читать будет)

Ответить