1 Востаннє редагувалося koala (16.06.2014 10:17:22)

Тема: Нащо потрібен protected?

Мене вкотре запитали, нащо потрібен модифікатор доступу protected. Звісно, я відправив людину в Гугл... але та повернулася звідти без результатів. І дійсно: побіжний пошук дає тільки опис, як працює protected, але не навіщо він взагалі потрібен. Принаймні, я за 5 хвилин пошуку українською, англійською і російською не знайшов прямої відповіді на це питання, тому розміщу відповідь тут (нарешті хоч щось українською буде знаходитися швидше, ніж російською :) ).
По-перше, нагадаю теорію. public-члени доступні звідусіль, protected-члени - з нащадків, private-члени - тільки з самого класу. public-спадкоємці не змінюють тип доступу, protected-спадкоємці змінюють всі public-члени на protected, private-спадкоємці все роблять private. Якщо не вказати тип доступу чи наслідування, то для класу він буде private, а для структури - public.
По-друге, нагадаю, що ви програмуєте не для себе. За півроку власний код для вас буде такий самий, як і чужий, і знання того, що його писали ви, не допоможе вам його читати, на відміну від якісного написання коду. Якщо ви пишете для себе - робіть все public і не ламайте собі голову, нащо вам обмежувати собі доступ до вашого ж коду? А от якщо не для себе - подумайте, як саме ваш код буде використовуватися. І тут є три варіанти:
- код суто локальний, призначений для забезпечення внутрішнього функціонування класу, не містить перевірки коректності параметрів. Це - private: його ви нікому не показуєте.
- код призначений для загального користування в стилі "шкільного Delphi" (використання класу, але не наслідування), він реалізує щось, явно притаманне цьому класу, визначає його поведінку, контролює ввід. Це - public: хай користуються всі, хто забажає.
- код призначений для подальшого наслідування, але не визначає зовнішньої поведінки. Тому, хто буде писати далі код спадкоємців, він знадобиться, але тим, хто просто створює екземпляри цього класу, робити з цим кодом нема чого (наприклад - код перевірки коректності даних). Ось це і є той самий випадок, коли потрібен protected.

Коротко можна сказати так: private-члени - це інтерфейс розробника, внутрішній. public-члени - це інтерфейс користувача, зовнішній. protected-члени - це інтерфейс майбутніх розробників, API вашого класу: користувачі його не бачать, але розробники можуть користуватися.

Також маю зауважити:
- не робіть змінні public! Також, бажано не робити їх protected, але не так суворо. Визначайте публічні геттери-сеттери замість прямого доступу.
- наслідування private - це некрасиво; швидше за все, якщо вам здається, що треба робити private-наслідування, насправді вам треба додати член цього типу до вашого класу. Це ж стосується і protected-наслідування.
- серед профі ведуться суперечки про доцільність protected в цілому. Є думка, що якщо щось треба застосовувати в потомках - треба робити його public, а якщо щось не треба показувати іншим - треба робити його private.

Довідково:
- нащо потрібен private;
- нащо потрібні friend-класи і функції;
- як взагалі добре оформлювати код на C++ (обговорення).

2

Re: Нащо потрібен protected?

"По-перше, нагадаю теорію. public-члени доступні звідусіль, protected-члени - з нащадків, private-члени - тільки з самого класу"
якщо уже бути точним- то protected доступні ще дружнім функціям і класам (хоча дружні класи та функції мають доступ до будь-якої секції)

3

Re: Нащо потрібен protected?

"Якщо не вказати тип доступу чи наслідування, то для класу він буде private, а для структури - public."
А для структури як у старому-доброму C )

4

Re: Нащо потрібен protected?

Про friend-ів - за посиланням читайте.
В старому доброму C наслідування немає.

Подякували: Regen, leofun012

5

Re: Нащо потрібен protected?

хм, про C істину глаголите) знав хіба, що за замовчуванням в C++ в структурі все оголошується як public, тобто так само як і в C
тому дякую)

6 Востаннє редагувалося Arete (30.10.2014 22:14:34)

Re: Нащо потрібен protected?

Наскільки я зрозумів питання стоїть не в тому нащо protected потрібен, а в тому де його можна використати. Приведу кілька прикладів де саме можна використати protected, всі вони взяті з класичних шаблонів програмування. В наведених нижче прикладах модифікатор доступу "protected" не є необхідним, але його використання є логічним та доречним, принаймні на мою думку. Поїхали!



Шаблон "Будівник"

Призначення:
Інкапсулює процес створення складного об’єкта для всіх підвидів цього об’єкта.

Ідея реалізації:
Для створення складного об’єкту Obj використовується спеціальний клас-будівник Builder, методи якого створюють якусь певну частину об’єкта Obj. Тобто якщо, наприклад, клас Obj це клас "Армія", то один метод Builder’а створює піхотинців, ще один лучників, інший квалерію, ну і ще якийсь - космічні винищувачі.

Процесом створення армії керує інший клас - Director. Директор приймає будівника як вхідний параметр і використовує його для створення армії, контролюючи процес створення. Тобто створив піхотинців, перевірив що все ок, створив лучників - перевірив... Якщо процес створення пройшов нормально то директор повертає створений об’єкт клієнту.

Оскільки армій може бути багато то сам клас "Армія" робиться як абстракний, що визначає інтерфейс взаємодії з армією, а вже від нього успадковуються різні армії - римська, грецька, єгипетська. Для кожної такої армії створюється свій будівник - тобто скільки армій стільки й будівників. Для того щоб всі будівники працювали однаково вони успадковуються від "верховного" будівника Builder, реалізуючи його інтерфейс.

Абстрактний будівник Builder містить в собі посилання на об’єкт армії, який він повертає після створення армії. Посилання є protected бо тільки успадковані від Builder’а будівники конкретних армій повинні мати доступ до об’єкту армії (оскільки вони її наповнюють). Всі інші об’єкти не повинні мати безпосередній доступ до армії, яку створює будівник.

Що це дає:
Клієнт, передаючи директору будівників різних армій, отримує різні армії, не володіючи інфомацією як саме вони створюються. Це дозволяє керувати складним процесом створення, добавляти нові підвиди об’єктів (нові армії в цьому випадку), не змінюючи код клієнта.

Приклад коду:

Прихований текст
#include <iostream>

using std::string;
using std::cout;
using std::cin;
using std::endl;


class Infantryman {
public:
  string info() { return "Infantryman"; }
};


class Archer {
public:
  string info() { return "Archer"; }
};


class Horseman {
public:
  string info() { return "Horseman"; }
};


class Catapult {
public:
  string info() { return "Catapult"; }
};


class Elephant {
public:
  string info() { return "Elephant"; }
};


// Class includes all army units
class Army {
public:
  Army() : infantryman( 0 ), archer ( 0 ), horseman( 0 ), catapult( 0 ), elephant( 0 ) {}
  ~Army();
  Infantryman* infantryman;
  Archer* archer;
  Horseman* horseman;
  Catapult* catapult;
  Elephant* elephant;
  string info() const ;
};


Army::~Army() {
  if( infantryman != 0 ) delete infantryman;
  if( archer != 0 ) delete archer;
  if( horseman != 0 ) delete horseman;
  if( catapult != 0 ) delete catapult;
  if( elephant != 0 ) delete elephant;
}

string Army::info() const {
  return ( ( infantryman != 0 ) ? infantryman->info() + '\n' : "" )
    + ( ( archer != 0 ) ? archer->info() + '\n' : "" )
    + ( ( horseman != 0 ) ? horseman->info() + '\n' : "" )
    + ( ( catapult != 0 ) ? catapult->info() + '\n' : "" )
    + ( ( elephant != 0 ) ? elephant->info() + '\n' : "" );
}



std::ostream& operator<< (std::ostream& out, const Army& army ) {
  out << army.info();
  return out;
}


class ArmyBuilder {
protected:
  Army* p;
public:
  ArmyBuilder() : p( 0 ) {}
  ~ArmyBuilder() {}
  virtual void createArmy() {}
  virtual void buildInfantryman() {}
  virtual void buildArcher() {}
  virtual void buildHorseman() {}
  virtual void buildCatapult() {}
  virtual void buildElephant() {}
  virtual Army* getArmy() { return p; }
};


class RomeArmyBuilder : public ArmyBuilder {
public:
  void createArmy() { p = new Army; }
  void buildInfantryman() { p->infantryman = new Infantryman(); }
  void buildArcher() { p->archer = new Archer(); }
  void buildHorseman() { p->horseman = new Horseman(); }
  void buildCatapult() { p->catapult = new Catapult(); }
};


class CarfagenArmyBuilder : public ArmyBuilder {
public:
  void createArmy() { p = new Army; }
  void buildInfantryman() { p->infantryman = new Infantryman(); }
  void buildArcher() { p->archer = new Archer();}
  void buildHorseman() { p->horseman = new Horseman(); }
  void buildElephant() { p->elephant = new Elephant(); }
};


class ArmyDirector {
public:
  Army* createArmy( ArmyBuilder& builder ) {
    builder.createArmy();
    builder.buildInfantryman();
    builder.buildArcher();
    builder.buildHorseman();
    builder.buildCatapult();
    builder.buildElephant();
    return builder.getArmy();
  }
};

void aBuilder() {
  RomeArmyBuilder romeBuilder;
  CarfagenArmyBuilder carfagenBuilder;
  ArmyDirector dir;

  Army* ra = dir.createArmy( romeBuilder );
  Army* ca = dir.createArmy( carfagenBuilder );
  cout << "Rome army:\n" << *ra << endl;
  cout << "Carthaginian army:\n" << *ca << endl << endl;

  delete ra;
  delete ca;
}


int main() {
  aBuilder();
  return 0;
}

Де можна почитати:
Е. Гамма Р. Хелм Р. Джонсон Дж. Вліссдес - Прийоми ООП проектування. ISBN 5-272-00355-1 Стор. 102



Шаблон "Прототип"

Призначення:
Задає види створюваних об’єктів за допомогою екземпляра-прототипа и створює нові об’єкти шляхом копіювання прототипу.

Ідея реалізації:
Ідея прототипу полягає в тому що десь існує "реєстр" об’єктів різних класів, які може створювати клієнт. Зазвичай реєстр це структура даних виду map з ключем "идентифікатор типу класа" і значеням - "екземпляр цього класу".

Кожен екземпляр, що знаходиться в реєстрі, по суті є прототипом на основі якого створюються інші об’єкти - об’єкти створюютьсям шляхо копіювання екземпляра. Для цього всі класи що є чи можуть бути в реєстрі є нащадками деякого інтерфейсу Prototype. В цьому інтерфейсі оголошена операція копіювання об’єкта - clone(), реалізована операція створення об’єкта по ІД, та також є статичні функції додавання та видалення прототипів з реєстру - addProtopype та removePrototype.

Створення об’єкта відбувається наступним чином - в метод створення передається ІД класу і по ІД реєстрі шукається екземпляр цього класу. Якщо екземпляр знайдений, то з нього робиться копія і вона повертається як результат.

Кожен підклас класу Prototype має статичний екземпляр свого виду, який автоматично добавляється в реєстр при ініціалізації статичних змінних програми, за допомогою метода addProtopype (в даному). Оскільки методи addProtopype та removePrototype ніде не використовуються окрім класів-нащадків, то вони оголошені як protected.

Що це дає:
Клієнт не залежить від виду об’єктів які він може створювати. Можна добавляти нові підвиди об’єктів, не змінюючи код клієнта, цей процес може бути навіть динамічним.

Приклад коду:

Прихований текст
#include <iostream>
#include <map>
#include <vector>

using std::string;
using std::cout;
using std::cin;
using std::endl;

enum Warrior_ID {Infantryman_ID, Archer_ID, Horseman_ID };

class Warrior; // forward declaration
typedef std::map< Warrior_ID, Warrior* > Registry;

Registry& getRegistry() {
  static Registry instance;
  return instance;
}


class Dummy{};

class Warrior {
public:
  virtual Warrior* clone() = 0;
  virtual string info() = 0;
  virtual ~Warrior() {}
  static Warrior* createWarrior( Warrior_ID id ) {
    Registry& r = getRegistry();
    if( r.find(id) != r.end() )
      return r[id]->clone();
    return 0;
  }
protected:
  static void addProtopype( Warrior_ID id, Warrior* prototype) {
    Registry& r = getRegistry();
    r[id] = prototype;
  }
  static void removePrototype( Warrior_ID id ) {
    Registry& r = getRegistry();
    r.erase( r.find( id ) );
  }
};


class Infantryman : public Warrior {
public:
  Warrior* clone() {
    return new Infantryman( * this );
  }
  string info() {
    return "Infantryman";
  }
private:
  Infantryman( Dummy ) {
    Warrior::addProtopype( Infantryman_ID, this );
  }
  Infantryman() {}
  static Infantryman prototype;
};


class Archer : public Warrior {
public:
  Warrior* clone() {
    return new Archer( * this );
  }
  string info() {
    return "Archer";
  }
private:
  Archer( Dummy ) {
    Warrior::addProtopype( Archer_ID, this );
  }
  Archer() {}
  static Archer prototype;
};


class Horseman : public Warrior {
public:
  Warrior* clone() {
    return new Horseman( * this );
  }
  string info() {
    return "Horseman";
  }
private:
  Horseman( Dummy ) {
    Warrior::addProtopype( Horseman_ID, this );
  }
  Horseman() {}
  static Horseman prototype;
};

Infantryman Infantryman::prototype = Infantryman( Dummy() );
Archer Archer::prototype = Archer( Dummy() );
Horseman Horseman::prototype = Horseman( Dummy() );



void aPrototype() {
  std::vector< Warrior* > v;
  v.push_back( Warrior::createWarrior( Infantryman_ID ) );
  v.push_back( Warrior::createWarrior( Archer_ID ) );
  v.push_back( Warrior::createWarrior( Horseman_ID ) );
  for( size_t i = 0; i < v.size(); ++i )
    cout << v[i]->info() << endl;
}

int main() {
  aPrototype();
  return 0;
}

З.І. Це один з варіантів реалізації шаблона прототип. Є ще варіант в якому прототипи зберігаються не в реєстрі, а в спеціальній "фабриці прототипів".


Де можна почитати:

Е. Гамма Р. Хелм Р. Джонсон Дж. Вліссдес - Прийоми ООП проектування. ISBN 5-272-00355-1 Стор. 121


Я нарахував використання protected в 9 шаблонах проектування, з 22.
Надіюсь, що комусь буде цікаво почитати цю писанинну. :)

7

Re: Нащо потрібен protected?

І тому це є погана мова.

8

Re: Нащо потрібен protected?

На Java чи C# буде аналогічно, imho. Ці шаблони проектування побудовані на принципах ООП, а не на особливостях мови.
Ложка теж поганий інструмент якшо не вміти нею користуватися. :)

9

Re: Нащо потрібен protected?

koala написав:

Принаймні, я за 5 хвилин пошуку українською, англійською і російською не знайшов прямої відповіді на це питання

google треба вміти використовувати. Мені вдалося за хвилину знайти ось таке наприклад:
http://www.dreamincode.net/forums/topic … -tutorial/
Див. "• IV. PRIVATE VERSUS PROTECTED MEMBERS"

10 Востаннє редагувалося quez (10.06.2015 13:05:43)

Re: Нащо потрібен protected?

mich_retten написав:
koala написав:

Принаймні, я за 5 хвилин пошуку українською, англійською і російською не знайшов прямої відповіді на це питання

google треба вміти використовувати. Мені вдалося за хвилину знайти ось таке наприклад:
http://www.dreamincode.net/forums/topic … -tutorial/
Див. "• IV. PRIVATE VERSUS PROTECTED MEMBERS"

Там немає про те, нащо потрібен protected. Там лише проблеми при його використанні.

Ви щось хочете довести, але у вас не виходить.

Подякували: koala1

11

Re: Нащо потрібен protected?

Будь ласка, знайдіть за вашим посиланням відповідь саме на моє питання, а не на дотичні.

12

Re: Нащо потрібен protected?

Тоді давайте напишемо статтю "навіщо потрібні int, коли є double", чи "навіщо потрібен do ... while, коли є while ... do". На кого ця "стаття" орієнтована? Що Ви нового пояснили, чого немає в підручнику? Якщо людина, прочитавши підручник і зрозумівши, як це функціонує, не може розібратися, для чого воно взагалі потрібне, то Ваша "стаття" цьому не зарадить. Марні зусилля. Ось що я "хочу довести" (а насправді просто висловити таке припущення).
Для чого потрібен автомобіль? Мені - щоб пересуватися і встигати куди мені треба, перевозити якісь речі. Комусь - щоб вкласти гроші, другим - щоб заробити гроші, іншим - щоб показати що він не пальцем роблений, ще іншим - "to keep up with joneses".

Подякували: leofun011

13

Re: Нащо потрібен protected?

mich_retten написав:

чого немає в підручнику?

в підручнику все професійно і описано словами мовою програмування, а тут "робітничо-селянською мовою" і зрозуміло

14

Re: Нащо потрібен protected?

А тепер давайте відкриємо підручник з фізики і почитаємо, що там про автомобіль написано. Що? Правильно - колеса, передачі, ДВЗ... а про встигання, гроші і суспільне визнання - ані слова. Програмуванню люди переважно вчаться за підручниками, а не зі спілкування з іншими людьми (і то слава Богу, що не навпаки). От я і закриваю пробіл. Просто склалася реальна ситуація, яку я описав вище, і мені здалося, що це буде корисним для інших людей. Вам не корисно? То проходьте, воно просто не для вас писано.

Подякували: quez, leofun01, sensei3

15

Re: Нащо потрібен protected?

reverse2500 написав:
mich_retten написав:

чого немає в підручнику?

в підручнику все професійно і описано словами мовою програмування, а тут "робітничо-селянською мовою" і зрозуміло

Можете пояснити, що було незрозуміло в підручнику, і що саме тепер Ви особисто нового зрозуміли?

16

Re: Нащо потрібен protected?

Часто використовую protected наслідування у такій ситуації (код дуже спрощено, суть збережена):

 
class cGlobalIO //Базовий абстрактний клас вводу/виводу
{
public:
    virtual bool Write() = 0;
    virtual bool Read() = 0;
    virtual ~cGlobalIO() {}
}

class cFile: public cGlobalIO // Клас читання/запису файлу
{
    HANDLE hFile;
public:
    bool Write() {}
    bool Read() {}
}

class cPCAPFile: protected cFile //клас роботи з PCAP файлом
{
public:
    bool ReadPDU() {}
    bool WritePDU() {}
}

Маємо клас сFile для роботи із файлами, у нього є public методи Write() та Read() для реалізації читання і запису масивів даних користувачем класу. Також маємо клас cPCAPFile, який використовує protected базування на cFile. Дана організація класів виходить з логіки їх призначення, адже об'єкт сPCAPFile - це фізично файл формату PCAP, в який не можна записувати довільні масиви даних. У свою чергу об'єкт класу cFile - це фізично звичайний файл, в який можна писати довільні дані. При роботі з об'єктами класу сPCAPFile  необхідно сховати від користувача методи Write() та Read(), щоб користувач не зміг записати довільних даних та не зіпсував структуру PCAP файлу, також забезпечуємо доступ до методів WritePDU() і ReadPDU() для реалізації функціоналу по суті. У свою чергу усі інші об'єкти класу cFile повинні давати доступ користувачам до методів Write() та Read() щоб забезпечувати запис довільних даних у файли. У нашому випадку protected наслідування якраз і забезпечує необхідний функціонал.

Подякували: 0xDADA11C7, koala2

17

Re: Нащо потрібен protected?

NetWorker, а можете ще уточнити, яке це має переваги перед private/protected елементом типу cFile в cPCAPFile, та перед private-наслідуванням?

Подякували: LoganRoss1

18 Востаннє редагувалося LoganRoss (23.05.2017 15:09:30)

Re: Нащо потрібен protected?

koala написав:

- серед профі ведуться суперечки про доцільність protected в цілому. Є думка, що якщо щось треба застосовувати в потомках - треба робити його public, а якщо щось не треба показувати іншим - треба робити його private.

Хочу трохи розказати про доцільність.
Члени даних, що мають модифікатор protected, недоступні за межами класу, за винятком деяких варіантів успадкування.

  • Відкриті (public) члени доступні в усьому коді програми.

  • Захищені (protected) члени доступні тільки всередині свого класу і деяких похідних класів в залежності від рівня доступу успадкування (public, protected).

  • Закриті (private) члени доступні тільки всередині свого класу, це означає, що вони недоступні безпосередньо в похідних класах.

Але якщо розглянути таке твердження:
Використання успадкування з модифікатором public означає, що відкриті члени базового класу стають відкритими членами похідного класу, захищені члени даних базового класу стають захищеними членами даних похідного класу, а закриті члени даних базового класу недоступні похідному класу.

Звідси випливає запитання, а навіщо взагалі protected, якщо можна отримати захищені члени через модифікатор public і нехай я не можу отримати безпосередньо закриті (private) члени даних, я можу отримати їх опосередковано. І це працює в злагоді з ідеєю інкапсуляції.

Здається, що в protected немає сенсу з вищеперелічених причин.

Однак, колись я писав гру і ось, наприклад код з неї:

class GenericPlayer : public Hand
{
    friend std::ostream& operator<<(std::ostream& os, const GenericPlayer& aGenericPlayer);
public:
    GenericPlayer(const std::string name = "");
    virtual ~GenericPlayer(); // dstr is auto virtal because this is inheritanced from class Hand. I just write virtual for remember it
    virtual bool IsHitting() const = 0; // show whether palyer want to continue take cards. This is pure virtual function means
                                        // that I will declare it in inheritance  classes with different declaration
    bool IsBusted() const; // return value when player have a bust - sum is more 21
    void Bust() const; // declare that player have a bust
protected:
    std::string m_Name;
};

Як видно, я використав модифікатор protected для змінної m_Name. Для чого? Доцільно використовувати так, наприклад, в абстрактному класі, як в даному прикладі.
В подальшому мені необхідно працювати з ім'ям гравця, що приймає m_Name, але працювати з нею в даному класі я не буду, та і m_Name приймає декілька імен, якщо гравців більше одного. Тому я використовую цю змінну в похідних класах. Public використовувати я не хочу, щоби випадково не змінити цю змінну в коді, наприклад (припустимо, що це не абстрактний клас і створимо об'єкт):

GenericPlayer player;
player.m_Name = "Name1" // є такий ризик, якщо програмер не спав всю ніч і не випив кави;

private як варіант, але це буде трохи громіздко, оскільки необхідно писати функцію для доступу до цієї змінної.
Тому зручно використати protected.

Звісно, можна обійтися без protected, але якщо воно є, то в деяких ситуаціях можна цим скористатися. Краще нехай буде, і можна не використовувати, ніж якщо раптом приспічить, а не буде.

Сам не знаю як так багато тексту вийшло.
P.S. Але може комусь цікаво.

19

Re: Нащо потрібен protected?

LoganRoss, а чим геттер та сеттер тут гірші, окрім 2-х додаткових рядків коду і відсутності ризику, що хтось не вип'є кави?

20

Re: Нащо потрібен protected?

koala написав:

LoganRoss, а чим геттер та сеттер тут гірші, окрім 2-х додаткових рядків коду і відсутності ризику, що хтось не вип'є кави?

Нічим не гірші, просто можна обійтися і без них. Я не кажу, що треба використовувати protected, навпаки, краще private. Але і protected потрібен, хоча дуже рідко, і я практично не знаю ситуації, в якій замість protected не можна було б використати public/private.