Конвенції щодо методів доступу (геттерів та сеттерів) у C ++


78

Декілька запитань про методи доступу в C ++ було задано на SO, але жоден не зміг задовольнити мою цікавість до цього питання.

Я намагаюся уникати доступу, коли це можливо, тому що, як Stroustrup та інші відомі програмісти, я вважаю клас з багатьма з них ознакою поганого ОО. У C ++ я в більшості випадків можу додати більше відповідальності до класу або використовувати ключове слово friend, щоб уникнути їх. Однак у деяких випадках вам дійсно потрібен доступ до певних учнів.

Є кілька можливостей:

1. Не використовуйте аксесуари взагалі

Ми можемо просто зробити відповідні змінні-члени загальнодоступними. У Java це заборонено, але, схоже, це нормально для спільноти C ++. Однак мене трохи турбують випадки, коли слід повернути явну копію або посилання на об'єкт лише для читання (const), це перебільшено?

2. Використовуйте методи отримання / встановлення в стилі Java

Я не впевнений, що це взагалі з Java, але я маю на увазі це:

int getAmount(); // Returns the amount
void setAmount(int amount); // Sets the amount

3. Використовуйте об’єктивні методи отримання / встановлення в стилі С

Це трохи дивно, але очевидно все частіше:

int amount(); // Returns the amount
void amount(int amount); // Sets the amount

Щоб це працювало, вам доведеться знайти інше ім'я для вашої змінної-члена. Деякі люди додають підкреслення, інші додають "m_". Мені теж не подобається.

Який стиль ви використовуєте і чому?


5
Якщо не брати до уваги лише невеликі структури даних, що створило враження, що в C ++ змінні загальнодоступні змінні будуть нормальними? Зазвичай це також заборона.
Georg Fritzsche

1
Так, це також заборонено в C ++. Але якби ви працювали з MFC, ви могли б припустити, що це буде нормально.
vobject

Останній раз обговорювався як [set / get methods in C ++] ( stackoverflow.com/questions/3632533 ), який я знайшов разом із serach "c ++ getter setter" . Див. Також [Getter та setter, вказівники чи посилання та хороший синтаксис для використання в c ++? ] ( stackoverflow.com/questions/1596432 ), стиль кодування геттерів / сетерів C ++ та, можливо, інші.
dmckee --- екс-модератор кошеня

4
Дивіться цю статтю (PDF), щоб дізнатись, чому геттери / сетери просто помиляються .
sbi

1
@vines: У реальному ОО інтерфейси об'єкта (визначені в класі статично набраними мовами) не повинні стосуватися стану об'єкта, а лише операцій, які надає об'єкт. Те, що ці операції змінюють стан об’єкта, є деталлю реалізації.
sbi

Відповіді:


44

З моєї точки зору, як сидячи з 4 мільйонами рядків коду на C ++ (і це лише один проект) з точки зору обслуговування, я б сказав:

  • Добре не використовувати геттери / сетери, якщо члени незмінні (тобто const) або прості без залежностей (як точковий клас з членами X та Y).

  • Якщо учасник privateлише, це також нормально, щоб пропустити getters / setters. Я також вважаю членів внутрішніх pimpl -класів так, privateніби блок .cpp малий.

  • Якщо член є publicабо protected( protectedнастільки ж поганий, як public) і не- const, непростий або має залежності, тоді використовуйте геттери / сетери.

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

Я віддаю перевагу стилю alternative 2., оскільки він є більш зручним для пошуку (ключовий компонент при написанні коду, що підтримується).


2
Приймати конкретну відповідь на запитання щодо спільноти wiki завжди трохи дивно, адже кожна відповідь допомагає. Однак я думаю, що ваш найкраще виправданий. В даний час я намагаюся уникати методів getter / setter, де це можливо. Потрібен деякий час, щоб придумати альтернативи, але вони часто кращі. Якщо мені потрібні прості дані, як ви згадали, я зазвичай використовую структуру, що містить лише дані.
Noarth

Якщо вам доводиться налагоджувати аксесуари, це просто ще одна причина не використовувати їх. Напевно, є ще цікаві місця для встановлення точки зупинку.
hoodaticus

1
Це чудовий момент, щоб сказати, що це корисно з точки зору налагодження, але я згоден, що, ймовірно, існують альтернативні способи отримання такої інформації. Крім того, що мої думки: "ОТРИМАННЯ І НАБОРИ" ніколи не використовувати разом. Багато хто вважає, що GET / SET дозволяє вам змінити його повернене значення, але тоді це не GET / SET, це метод, що виконує певний набір дій, і його слід називати повноцінним для поточного процесу.
Джеремі Тріфіло

9

2) є найкращим ІМО, оскільки робить ваші наміри чіткішими. set_amount(10)є більш значущим, ніж amount(10), і як приємний побічний ефект дозволяє член з іменем amount.

Відкриті змінні, як правило, погана ідея, оскільки інкапсуляції немає. Припустимо, вам потрібно оновити кеш чи оновити вікно при оновленні змінної? Шкода, якщо ваші змінні є загальнодоступними. Якщо у вас є встановлений метод, ви можете додати його туди.


2
Канонічна інкапсуляція означає приховування деталей внутрішнього стану та поведінки логічної одиниці. Якщо ви отримуєте доступ до приватного члена даних через загальнодоступний інтерфейс - тоді ви вже порушуєте інкапсуляцію, незалежно від того, що робите це за допомогою аксесуарів. Добре інкапсульовані класи не мають доступу, натомість вони мають активні методи, які використовують та впливають на внутрішній стан публічно невидимими способами.
ulidtko

1
Що стосується сценарію "змінених вимог", наприклад, мутації кешу або оновлення інтерфейсу користувача, або надсилання мережевого запиту всередині getId()виклику: вкладаючи цю логіку в геттер / сеттер, ви порушуєте контракт методу доступу. Ви не порушите компіляцію клієнтського коду таким чином, але ви, звичайно, порушите його роботу, що ще гірше. Оскільки ніхто в здоровому стані думки не припустить, що ваш геттер може, скажімо, іноді розподіляти пам’ять, або ваш сеттер може писати у файлову систему. У всіх цих сценаріях вам потрібно переробити нові вимоги.
ulidtko

№1 робить ваші наміри чіткішими, а не №2. №2 робить вигляд, ніби він інкапсулює внутрішній стан, роблячи змінну приватною, а потім скасовує всю її роботу, приховуючи цей стан, дозволяючи будь-кому через загальнодоступні користувачі читати та писати цю змінну. No2 - це не випадок явних намірів, це просто гра-оболонка.
hoodaticus

7
  1. Я ніколи не використовую цей стиль. Оскільки це може обмежити майбутнє вашого дизайну класу, і явні гетери або сетери настільки ж ефективні, як і хороші компілятори.

    Звичайно, насправді вбудовані явні геттери або сетери створюють стільки ж залежності від реалізації класу. Вони просто зменшують семантичну залежність. Вам все одно доведеться перекомпілювати все, якщо ви їх змінили.

  2. Це мій стиль за замовчуванням, коли я використовую методи доступу.

  3. Цей стиль здається мені занадто «розумним». Я використовую його в рідкісних випадках, але лише у тих випадках, коли я дійсно хочу, щоб аксесуар відчував себе якомога більше як змінна.

Я думаю, що є випадок, коли прості пакети змінних з можливо конструктором переконуються, що всі вони ініціалізовані до чогось розумного. Коли я роблю це, я просто роблю це structі залишаю все відкритим.


4
+1 для "занадто розумних". Я б рекомендував не зловживати можливістю перевантаження, це просто ускладнює читачів, а також важче шукати в кодовій базі.
Matthieu M.

-1 За "обмеження майбутнього дизайну вашого класу тощо". Ви розробляєте клас таким, яким він є зараз. Якщо ви хочете переробити його пізніше, зробіть це. Що, якщо це поле зникне в майбутньому? Аксесуар вам не допоможе. І якщо ви збережете його, повернувши якесь сміттєве значення, у вас можуть виникнути тихі поломки, про які ви не знаєте. Не надмірно проектуйте, інакше у вас може бути просто карта імен полів до вартості і закінчити з нею.
einpoklum

Чому ви хотіли б, щоб "аксесуар відчував себе якомога більше змінною"? Вони не є змінними, чому ви хочете заплутати когось, щоб він думав, що вони є?
deetz

@deetz - Ну, властивість @ здається досить популярною в Python. І це спосіб невидимості перетворити змінний доступ у виклик функції.
Всезнайкий

6
  1. Це хороший стиль, якщо ми просто хочемо представити pureдані.

  2. Мені це не подобається :), бо get_/set_це справді непотрібно, коли ми можемо перевантажити їх у C ++.

  3. STL використовує цей стиль, такий як std::streamString::strі std::ios_base::flags, за винятком випадків, коли цього слід уникати! коли? Коли ім'я методу суперечить імені іншого типу, тоді get_/set_використовується стиль, наприклад, std::string::get_allocatorчерез std::allocator.


4
Я боюся, що STL може бути дещо непослідовним у цьому відношенні. На жаль, сам Страуструп, схоже, не висловив думки щодо того, як робити геттери / сеттери, за винятком того, що він не любить заняття з багатьма з них.
Noarth

4

Взагалі, я вважаю, що не дуже добре мати занадто багато геттерів та сеттерів, які використовуються занадто багатьма сутностями в системі. Це лише вказівка ​​на поганий дизайн або неправильну інкапсуляцію.

Сказавши, що якщо такий дизайн потребує реконструкції та доступний вихідний код, я вважаю за краще використовувати шаблон Visitor Design. Причиною є:

a. Це дає класу можливість вирішити, кому дозволити доступ до його приватної держави

b. Це дає класу можливість вирішити, який доступ дозволити кожному з суб'єктів, які зацікавлені в його приватній державі

c. Він чітко документує такий розширений доступ через чіткий інтерфейс класу

Основна ідея:

а) Переробляйте дизайн, якщо це можливо,

б) Рефактор такий, що

  1. Весь доступ до стану класу здійснюється через добре відомий індивідуалістичний інтерфейс

  2. Повинна бути передбачена можливість налаштувати який - то робити і Етикет для кожного такого інтерфейсу, наприклад , доступ із зовнішнього об'єкта ДОБРА має бути дозволено, всі доступи від зовнішнього об'єкта BAD повинні бути анульовані, і зовнішній об'єкт ОК має бути дозволено отримати , але не встановлено (наприклад)


Дійсно цікавий момент, немає уявлення, чому це було проголосовано проти. Але я думаю, що я б віддав перевагу ключовому слову friend, якщо певний клас потребує доступу до певних учасників.
fhd

2
  1. Я б не виключав доступ з використання. Може для деяких структур POD, але я вважаю їх непоганою справою (деякі аксесуари також можуть мати додаткову логіку).

  2. Якщо ви узгоджуєтесь зі своїм кодом, це не має значення принципу іменування. Якщо ви використовуєте кілька сторонніх бібліотек, вони все одно можуть використовувати різні правила іменування. Тож це справа смаку.


1

Додатковою можливістю може бути:

int& amount();

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

str.length() = 5; // Ok string is a very bad example :)

Іноді це просто хороший вибір:

image(point) = 255;  

Ще одна можливість - використовувати функціональну нотацію для модифікації об’єкта.

edit::change_amount(obj, val)

Таким чином небезпечна / редагуюча функція може бути вилучена в окремий простір імен із власною документацією. Здається, це цілком природно із загальним програмуванням.


Я не проголосував за, але перша - дуже погана відповідь, оскільки вона розкриває деталі дизайну, які звичайний аксесуар тримає прихованими. А інша пропозиція недостатньо варта, щоб викупити відповідь. Якщо ви збираєтеся спробувати щось на зразок першого стилю, принаймні поверніть якийсь об'єкт, який має operator =замість посилання на внутрішній елемент даних.
Всезначний

Щоб все було зрозуміліше, моя відповідь лише доповнює перелік можливостей, розпочатих ОП (можливо, мені слід зауважити). Я особливо не рекомендую ці можливості. Як сказав Omnifarious, використання перевантаження класу проксі operator=в першому випадку, очевидно, добре робити як мінімум. Я б використав другий у деяких випадках, якщо хочу надати функцію редагування, поперечну ієрархії класу, і коли це має сенс чітко відокремити цю функціональність від класу. Це, мабуть, погана ідея для простого сеттера.
log0

1

Я бачив ідеалізацію класів замість інтегральних типів для посилання на значущі дані.

Щось подібне нижче, як правило, погано використовує властивості C ++:

struct particle {
    float mass;
    float acceleration;
    float velocity;
} p;

Чому? Оскільки результат p.mass * p. прискорення - це плавання, а не сила, як очікувалося.

Визначення класів для позначення мети (навіть якщо це значення, як сума, згадана раніше) має більше сенсу і дозволяє нам робити щось на зразок:

struct amount
{
    int value;

    amount() : value( 0 ) {}
    amount( int value0 ) : value( value0 ) {}
    operator int()& { return value; }
    operator int()const& { return value; }
    amount& operator = ( int const newvalue )
    {
        value = newvalue;
        return *this;
    }
};

Ви можете отримати доступ до значення в кількості неявно за допомогою оператора int. Крім того:

struct wage
{
    amount balance;

    operator amount()& { return balance; }
    operator amount()const& { return balance; }
    wage& operator = ( amount const&  newbalance )
    {
        balance = newbalance;
        return *this;
    }
};

Використання геттера / сеттера:

void wage_test()
{
    wage worker;
    (amount&)worker = 100; // if you like this, can remove = operator
    worker = amount(105);  // an alternative if the first one is too weird
    int value = (amount)worker; // getting amount is more clear
}

Це інший підхід, не означає, що він хороший чи поганий, але різний.


0

Дозвольте розповісти про ще одну додаткову можливість, яка видається найбільш лаконічною.

Потрібно прочитати та змінити

Просто оголосіть цю змінну загальнодоступною:

class Worker {
public:
    int wage = 5000;
}

worker.wage = 8000;
cout << worker.wage << endl;

Потрібно просто прочитати

class Worker {
    int _wage = 5000;
public:
    inline int wage() {
        return _wage;
    }
}

worker.wage = 8000; // error !!
cout << worker.wage() << endl;

Недоліком цього підходу є те, що вам потрібно змінити весь викличний код (тобто додати дужки), коли ви хочете змінити шаблон доступу.


0

варіація №3, мені кажуть, це може бути "вільний" стиль

class foo {
  private: int bar;
  private: int narf;
  public: foo & bar(int);
  public: int bar();
  public: foo & narf(int);
  public: int narf();
};

//multi set (get is as expected)
foo f; f.bar(2).narf(3);

Це нормально з точки зору функціональності, але дещо виходить за рамки питання. В основному це геттер / сеттер стилю c # з можливістю ланцюжка наборів. Я чув, що цей шаблон не часто використовується, і я знаю, що його, звичайно, легко замінити. Питання також вимагає пояснення, чому ви використовуєте цей шаблон над іншими, якщо ви хочете дати його.
Matias Chara
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.