Це трохи езотерично сказати, як ви вже впізнали, що могло б мене на хвилину почухати голову, коли я вперше зіткнувся з вашим кодом, цікавившись, що ви робите, і де реалізуються ці допоміжні класи, поки я не почну підбирати ваш стиль / звички (в цей момент я можу повністю звикнути до цього).
Мені подобається, що ти зменшуєш кількість інформації в заголовках. Особливо у дуже великих кодових базах, які можуть мати практичний ефект для зменшення залежностей від компіляції та в кінцевому підсумку для побудови часу.
Моя реакція кишки полягає в тому, що якщо ви відчуваєте необхідність приховати деталі реалізації таким чином, надайте перевагу переходу параметра до вільно стоячих функцій із внутрішнім зв’язком у вихідному файлі. Зазвичай ви можете реалізувати корисні функції (або цілі класи), корисні для реалізації певного класу, не маючи доступу до всіх внутрішніх класів і замість цього просто передати відповідні з реалізації методу до функції (або конструктора). І природно, що це бонус за зменшення зв’язку між вашим класом та "помічниками". Він також має тенденцію до узагальнення того, що в іншому випадку могло б бути "помічниками" далі, якщо ви виявите, що вони починають служити більш узагальненій цілі, застосовній до більш ніж одного класу реалізації.
Я також іноді трохи стискаюся, коли бачу в коді багато "помічників". Це не завжди правда, але іноді вони можуть проявляти симптом для розробника, який просто розкладає функції вольово-невільно для усунення дублювання коду величезними крапками даних, переданими навколо функцій з ледь зрозумілими іменами / цілями, крім того, що вони зменшують кількість код, необхідний для реалізації деяких інших функцій. Лише трохи підлітковий роздум заздалегідь може призвести до набагато більшої чіткості з точки зору того, як реалізація класу розкладається на подальші функції, а сприяння передачі конкретних параметрів передачі цілих примірників вашого об'єкта навколо з повним доступом до внутрішніх даних може допомогти просувати цей стиль дизайнерської думки. Я не пропоную вам це робити, звичайно (я поняття не маю),
Якщо це стане непростим, я б розглядав друге, ідіоматичне рішення, яке є pimpl (я розумію, ви з цим згадали проблеми, але, думаю, ви можете узагальнити рішення, щоб уникнути тих, хто докладе мінімум зусиль). Це може перемістити багато інформації, яку ваш клас потребує впровадити, включаючи його приватні дані далеко від заголовка. Проблеми продуктивності pimpl можна значною мірою пом'якшити за допомогою брудового розподільника постійного часу *, як безкоштовного списку, зберігаючи семантику значень без необхідності впровадження повномасштабного визначеного користувачем копіюючого копію.
- Для аспекту продуктивності pimpl принаймні вводить накладні накладні, але я думаю, що випадки повинні бути досить серйозними, коли це викликає практичне занепокоєння. Якщо просторова локалізація не значно зменшиться через алокатор, то ваші щільні петлі, що повторюються над об'єктом (який, як правило, має бути однорідним, якщо продуктивність викликає велике занепокоєння), як і раніше, як правило, мінімізують пропуски кешу, якщо ви використовуєте щось подібне вільний список для виділення pimpl, розміщуючи поля класу в значною мірою суміжні блоки пам'яті.
Особисто лише після вичерпання цих можливостей я би розглядав щось подібне. Я думаю, що це пристойна ідея, якщо альтернатива є як більш приватні методи, піддані заголовку, і, можливо, лише езотеричний характер викликає практичне значення.
Альтернатива
Одна з альтернатив, що впала мені в голову саме зараз, яка значною мірою виконує ваші ті самі цілі відсутніх друзів, така:
struct PredicateListData
{
int somePrivateField;
};
class PredicateList
{
PredicateListData data;
public:
bool match() const;
};
// In source file:
static bool fullMatch(const PredicateListData& p)
{
// Can access p.somePrivateField here.
}
bool PredicateList::match() const
{
return fullMatch(data);
}
Тепер це може здатися суперечливою різницею, і я все-таки називаю це "помічником" (можливо в зневажливому сенсі, оскільки ми все ще передаємо весь внутрішній стан класу у функцію, потрібен він все це чи ні) крім того, це не дозволяє уникнути "шокового" фактора зустрічі friend
. Взагалі friend
виглядає трохи страшно бачити часто відсутніх подальших перевірок, оскільки це говорить про те, що внутрішні особи вашого класу доступні в інших місцях (що означає те, що він може бути нездатним підтримувати власні інваріанти). З тим, як ви користуєтеся friend
цим, стає досить суперечливо, якщо люди знають про практику з тих пірfriend
просто перебуває в одному і тому ж вихідному файлі, що допомагає реалізувати приватну функціональність класу, але вищезазначене досягає майже однакового ефекту хоча б з однією можливою суперечливою перевагою, яка не залучає друзів, що уникає такого роду ("О стріляйте, у цього класу є друг. Де ще доступ до мутантів / мутацій? "). Враховуючи, що вищенаведена версія одразу повідомляє, що приватним особам немає жодного способу доступу / мутації за межами нічого, що робиться під час здійснення PredicateList
.
Це, можливо, рухається до дещо догматичних територій з таким рівнем нюансів, оскільки будь-хто може швидко зрозуміти, якщо ви рівномірно назвати речі *Helper*
і помістити їх у один і той же вихідний файл, що все це в поєднанні разом як частина приватної реалізації класу. Але якщо ми отримаємо вибагливий ніт, то, можливо, безпосередньо вище стиль не викличе стільки реакцій на коліна на перший погляд, відсутнє friend
ключове слово, яке, як правило, виглядає трохи страшно.
Для інших питань:
Споживач може визначити свій клас PredicateList_HelperFunctions і дозволити їм отримати доступ до приватних полів. Хоча я не вважаю це величезною проблемою (якщо ви дуже хотіли на тих приватних полях, ви могли б зробити кастинг), можливо, це спонукає споживачів використовувати його таким чином?
Це може бути можливість через кордони API, коли клієнт міг би визначити другий клас з тим же ім'ям і отримати доступ до внутрішніх даних таким чином без помилок зв’язку. Знову ж таки, я здебільшого C-кодер, що працює в графіці, де проблеми безпеки на цьому рівні "що робити" дуже низькі в списку пріоритетів, тому такі проблеми, як я, - це лише ті, на які я маю махати руками і займатися танцями і спробуйте зробити вигляд, ніби їх не існує. :-D Якщо ви працюєте в такій галузі, де такі проблеми є досить серйозними, я думаю, що це варто врахувати.
Наведена вище альтернативна пропозиція також уникає страждання цим питанням. Якщо ви все ще хочете дотримуватися використання, friend
проте, ви також можете уникнути цього питання, зробивши помічник приватним вкладеним класом.
class PredicateList
{
...
// Declare nested class.
class Helper;
// Make it a friend.
friend class Helper;
public:
...
};
// In source file:
class PredicateList::Helper
{
...
};
Це загальновідома модель дизайну, для якої існує назва?
Наскільки мені невідомо. Я сумніваюся, що це буде один, оскільки він дійсно потрапляє в деталі деталей та стилю реалізації.
"Помічник пекла"
У мене з’явився запит на додаткове роз’яснення щодо того, як я іноді стискаюсь, коли бачу реалізацію з великою кількістю «помічника» коду, і це може бути дещо суперечливим, але це фактично фактично, як я насправді робив переслідування, коли я налагоджував деякі моїх колег реалізація класу тільки для пошуку вантажів "помічників". :-D І я не був єдиним в команді, який чухав голову, намагаючись зрозуміти, що саме повинні робити ці помічники. Я також не хочу виходити з догматики на кшталт "Не використовуй помічників", але я б зробив крихітну пропозицію, що це може допомогти подумати про те, як реалізувати відсутні речі, коли це можливо.
Чи не всі функції приватних членів помічники функцій за визначенням?
І так, я включаю приватні методи. Якщо я бачу , клас з як простим відкритим інтерфейсом , але , як нескінченний набір приватних методів , які кілька погано певним в мети , як find_impl
або find_detail
чи find_helper
, то я також плазувати подібним чином.
Те, що я пропоную як альтернативу, - це недружні функції, що не мають друзів, із внутрішнім зв’язком (оголошеним static
або всередині анонімного простору імен), щоб допомогти реалізувати свій клас принаймні більш узагальненою метою, ніж "функція, яка допомагає реалізувати інші". І я можу навести тут Herb Sutter з C ++ «Стандартів кодування», чому це може бути кращим із загальної точки зору SE:
Уникайте членських внесків: Де можливо, віддайте перевагу функціям, які не належать до членів. [...] Функції, що не належать до друзів, покращують інкапсуляцію, зводячи до мінімуму залежності: Тіло функції не може залежати від непублічних членів класу (див. Пункт 11). Вони також розбивають монолітні класи, щоб звільнити роздільну функціональність, ще більше зменшуючи зв'язок (див. Пункт 33).
Ви також можете зрозуміти "членські внески", про які він говорить певною мірою з точки зору основного принципу звуження змінної сфери. Якщо ви уявляєте, як самий крайній приклад, об’єкт «Бог», у якого є весь код, необхідний для запуску всієї вашої програми, то надайте перевагу «таким помічникам» (функціям, чи то членам, чи друзям), які можуть отримати доступ до всіх внутрішніх справ ( приватні особи) класу в основному роблять ці змінні не менш проблемними, ніж глобальні змінні. У вас є всі труднощі з правильним керуванням державою та безпекою потоку та підтримкою інваріантів, які ви отримаєте із глобальними змінними в цьому найвиразнішому прикладі. І звичайно, більшість реальних прикладів, сподіваємось, не є десь близькими до цієї крайності, але приховування інформації є настільки ж корисним, як і обмеження обсягу інформації, до якої можна отримати доступ.
Тепер Саттер вже дає приємне пояснення тут, але я також додам, що розв'язка має тенденцію сприяти психологічному поліпшенню (принаймні, якщо ваш мозок працює як мій) з точки зору того, як ви проектуєте функції. Коли ви починаєте проектувати функції, які не можуть отримати доступ до всього класу, за винятком лише відповідних параметрів, які ви передаєте, або, якщо ви передаєте екземпляр класу як параметр, лише його загальнодоступним членам, він прагне сприяти дизайнерському мисленню, що сприяє функції, які мають більш чітке призначення, крім роз’єднання та сприяння вдосконаленому капсулуванню, ніж те, що ви в іншому випадку могли б спокусити розробити, якби ви могли просто отримати доступ до всього.
Якщо ми повернемося до кінцівок, то база коду, пронизана глобальними змінними, не точно спокушає розробників проектувати функції зрозумілим та узагальненим за призначенням. Дуже швидко, чим більше інформації ви можете отримати в функції, тим більшість із нас смертних стикаються з спокусою її знеструмити і зменшити її чіткість на користь доступу до всієї цієї додаткової інформації, яку ми маємо замість прийняття більш конкретних і релевантних параметрів для цієї функції. звузити доступ до держави та розширити його застосовність та покращити ясність намірів. Це стосується (хоча взагалі в меншій мірі) функцій членів або друзів.