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

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

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

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

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

class some {
  friend void f(some&);
public:
  int a_;
protected:
  int b_;
private:
  int c_;
};

void f(some& obj) {
  obj.a_ = 0; // OK
  obj.b_ = 0; // OK
  obj.c_ = 0; // OK
}

void g(some& obj) {
  obj.a_ = 0; // OK
  obj.b_ = 0; // compile time error
  obj.c_ = 0; // compile time error
}

class derived : public some {
  derived() {
    a_ = 0; // OK
    b_ = 0; // OK
    c_ = 0; // compile time error
  }
};

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

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

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

class some {
public:
  int a;
protected:
  int b;
private:
  int c;
};

class derived : public some {
public:
  using some::b;
};

void f(derived& obj) {
  obj.b = 0; // OK
}

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

  • Модифицировать определение класса, добавив друга (функцию или класс)
    class some {
      friend class some_friend;
    public:
      int a;
    protected:
      int b;
    private:
      int c;
    };
    
    class some_friend {
    public:
      static void hack(some& obj) {
        obj.c = 0;
      }
    };
    
  • Воспользоваться препроцессором:
    #define private public
    
    class some {
    public:
      int a;
    protected:
      int b;
    private:
      int c;
    };
    
    void hack(some& obj) {
      obj.c = 0;
    }
    
  • Создать класс с таким же расположением в памяти и воспользоваться reinterpret_cast для преобразования указателей:

    class some {
    public:
      int a;
    protected:
      int b;
    private:
      int c;
    };
    
    class hack_some {
    public:
      int a;
      int b;
      int c;
    };
    
    void h(some& obj) {
      reinterpret_cast<hack_some*>(&obj)->c = 0;
    }
    
  • Если у «взламываемого» класса есть шаблонная функция, можно её специализировать своим типом:

    class some {
    public:
      int a;
      template<class T> void func(void) {
        a = b + c;
      }
    protected:
      int b;
    private:
      int c;
    };
    
    class hack_template_param{};
    
    template<>
    void some::func<hack_template_param>(void) {
      c = 0;
    }
    
    void hack(void) {
      some o;
      o.func<hack_template_param>();
    };
    

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

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

UPD (31.08.2013): Для компиляторов GCC и CLANG существует опция -fno-access-control, которая позволяет отключить контроль доступа к членам класса со стороны компилятора.

UPD (31.08.2013): Переформатировал исходный код под использование встроенного плагина вордпресса.

Advertisements