Внутрішні типи в C ++ - хороший стиль чи поганий стиль?


179

Щось, чому я останнім часом часто зустрічаюсь, - це оголошення typedefs, що стосуються певного класу всередині цього класу, тобто

class Lorem
{
    typedef boost::shared_ptr<Lorem> ptr;
    typedef std::vector<Lorem::ptr>  vector;

//
// ...
//
};

Ці типи потім використовуються в іншому місці коду:

Lorem::vector lorems;
Lorem::ptr    lorem( new Lorem() );

lorems.push_back( lorem );

Причини, які мені подобаються:

  • Він зменшує шум, що вводиться шаблонами класів, std::vector<Lorem>стає Lorem::vectorтощо.
  • Він служить як заява про наміри - у наведеному вище прикладі клас Lorem призначений для посилання, що підраховується через boost::shared_ptrі зберігається у векторі.
  • Це дозволяє змінювати реалізацію - тобто якщо Лорему потрібно було змінити, щоб настирливо посилатись (через boost::intrusive_ptr) на наступному етапі, то це мало б мінімальний вплив на код.
  • Я думаю, що це виглядає «красивіше» і його, мабуть, легше читати.

Причини, які мені не подобаються:

  • Іноді виникають проблеми із залежностями - якщо ви хочете вставити, скажімо, Lorem::vectorв інший клас, але потрібно лише (або хочете) переслати оголошення Lorem (на відміну від введення залежності у його заголовок), тоді вам доведеться використовувати явні типи (наприклад, boost::shared_ptr<Lorem>а не Lorem::ptr), що трохи непослідовно.
  • Це може бути не дуже часто, а значить, важче зрозуміти?

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

Відповіді:


153

Я вважаю, що це відмінний стиль, і я ним сам користуюся. Завжди краще максимально обмежити область імен, а використання класів - найкращий спосіб зробити це в C ++. Наприклад, бібліотека стандарту C ++ широко використовує typedefs в межах класів.


Це хороший момент, я дивуюсь, що виглядати «красивіше» було моєю підсвідомістю делікатно, вказуючи, що обмежена сфера - це хороша річ. Мені цікаво, але чи той факт, що STL використовує його переважно в шаблонах класів, робить його дещо іншим використанням? Чи важче виправдатись у «конкретному» класі?
Буде Бейкер

1
Ну і стандартна бібліотека складається з шаблонів, а не класів, але я думаю, що виправдання однакове для обох.

14

Він служить заявою про наміри - у наведеному вище прикладі клас Lorem призначений для посилання, що підраховується через boost :: shared_ptr і зберігається у векторі.

Це саме те, що він не робить.

Якщо я бачу в коді "Foo :: Ptr", я абсолютно не маю уявлення, чи це спільний_ptr або Foo * (у STL є: typedefs вказівника, що є T *, пам’ятайте) або будь-що інше. Esp якщо це спільний покажчик, я взагалі не надаю typedef, але зберігаю явно використовувати код shared_ptr у коді.

Насправді я навряд чи використовую typedefs поза метапрограмуванням шаблонів.

STL робить цей тип речі весь час

Дизайн STL з поняттями, визначеними з точки зору функцій-членів та вкладених typedefs - це історичний культ, сучасні бібліотеки шаблонів використовують вільні функції та класи ознак (пор. Boost.Graph), оскільки вони не виключають вбудованих типів із моделювання концепції та тому, що це спрощує адаптацію типів, які не були розроблені з урахуванням концепцій бібліотек шаблонів.

Не використовуйте STL як причину робити однакові помилки.


Я погоджуюся з вашою першою частиною, але ваша недавня редакція трохи короткозора. Такі вкладені типи спрощують визначення класів ознак, оскільки вони забезпечують розумний дефолт. Розглянемо новий std::allocator_traits<Alloc>клас ... вам не доведеться спеціалізувати його для кожного окремого алокатора, який ви пишете, оскільки він просто запозичує типи безпосередньо у Alloc.
Dennis Zickefoose

@Dennis: У C ++ зручність має бути на стороні / користувача / бібліотеки, а не на стороні її / автора /: користувач бажає рівномірного інтерфейсу для ознаки, і лише клас класу може надати це, через наведені вище причини). Але навіть як Allocавтор, спеціалізуватися std::allocator_traits<>на його новому типі не зовсім важче, ніж додати потрібні typedefs. Я також відредагував відповідь, оскільки моя повна відповідь не вписувалася в коментар.
Marc Mutz - mmutz

Але це на стороні користувача. Як користувач в allocator_traitsспробі створити користувальницький аллокатор, я не турбувати з п'ятнадцятьма членами класу риси ... все , що потрібно зробити , це сказати , typedef Blah value_type;і надати відповідні функції - членів, і за замовчуванням allocator_traitsбуде з'ясувати відпочинок. Далі подивіться на ваш приклад Boost.Graph. Так, він широко використовує клас ознак ... але реалізація за замовчуванням graph_traits<G>просто запитів Gдля власних внутрішніх typedefs.
Dennis Zickefoose

1
І навіть стандартна бібліотека 03 використовує класи ознак, де це доречно ... філософія бібліотеки полягає не в тому, щоб працювати на контейнерах загалом, а працювати на ітераторах. Таким чином, він забезпечує iterator_traitsклас, щоб ваші загальні алгоритми могли легко запитувати відповідну інформацію. Що, знову ж таки, за замовчуванням запитує ітератор на власну інформацію. Довгий і короткий факт полягає в тому, що риси і внутрішні типи навряд чи взаємовиключні ... вони підтримують одне одного.
Dennis Zickefoose

1
@Dennis: iterator_traitsстало необхідним тому, що T*має бути моделлю RandomAccessIterator, але ви не можете вводити потрібні typedefs T*. Як тільки ми мали iterator_traits, вкладені typedefs стали надлишковими, і я б хотів, щоб їх потім видаляли. З тієї ж причини (неможливість додати внутрішній typedefs) T[N]не моделює Sequenceконцепцію STL , і вам потрібні ключі, такі як std::array<T,N>. Boost.Range показує, як можна визначити сучасну концепцію послідовності, яка T[N]може моделювати, оскільки вона не потребує вкладених typedefs, а також функцій членів.
Марк Муц - mmutz


8

Typdefs, безумовно, хороший стиль. І всі ваші "причини, які мені подобаються" - хороші і правильні.

Про проблеми, які у вас є з цим. Ну, авансова декларація - це не святий грааль. Ви можете просто розробити код, щоб уникнути багаторівневих залежностей.

Ви можете перемістити typedef поза класом, але Class :: ptr настільки гарніший, ніж ClassPtr, що я цього не роблю. Це як з просторами імен, як на мене - речі залишаються пов'язаними в межах сфери.

Іноді я

Trait<Loren>::ptr
Trait<Loren>::collection
Trait<Loren>::map

І це може бути за замовчуванням для всіх класів домену та з певною спеціалізацією для певних.


3

STL постійно займається цим типом речі - typedefs є частиною інтерфейсу для багатьох класів STL.

reference
iterator
size_type
value_type
etc...

- це всі typedefs, які є частиною інтерфейсу для різних класів шаблонів STL.


Щоправда, і я підозрюю, що саме тут я вперше взяв його. Здається, що це було б трохи легше виправдати? Я не можу не врахувати typedefs в шаблоні класу як більш схожий на змінні, якщо вам трапляється думати по лінії "метапрограмування".
Буде Бейкер

3

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

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


3

В даний час я працюю над кодом, який інтенсивно використовує такі типи typedefs. Поки це добре.

Але я помітив, що досить часто є ітеративні typedefs, визначення розділені між кількома класами, і ви ніколи не знаєте, з яким типом ви маєте справу. Моє завдання - узагальнити розмір деяких складних структур даних, прихованих за цими typedefs - тому я не можу покластися на існуючі інтерфейси. У поєднанні з трьома-шести рівнями вкладених просторів імен, і тоді це стає заплутаним.

Тому перед їх використанням слід врахувати деякі моменти

  • Хтось ще потребує цих типів? Чи багато використовується класом в інших класах?
  • Чи я скорочую використання чи приховую клас? (У разі приховування ви також можете подумати про інтерфейси.)
  • Чи працюють інші люди з кодом? Як вони це роблять? Вони вважатимуть, що це простіше, чи заплутаються?

1

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

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


Це призведе до анонімного простору імен з typedefs, чи не так ?! Проблема typedef полягає в тому, що він приховує фактичний тип, який може викликати конфлікти при включенні в / через декілька модулів, які важко знайти / виправити. Це є гарною практикою містити їх у просторах імен або всередині класів.
Indy9000

3
Конфлікти імен та анонімні простори імен не мають нічого спільного із збереженням імені типу всередині класу чи поза ним. Ви можете конфліктувати імена зі своїм класом, а не з вашими typedefs. Тому, щоб уникнути полюції імен, використовуйте простори імен. Заявіть про свій клас та пов'язані з ним типи в просторі імен.
Cătălin Pitiș

Ще один аргумент для розміщення typedef всередині класу - це використання шаблонованих функцій. Коли, скажімо, функція отримує невідомий тип контейнера (вектор або список), що містить невідомий тип рядка (рядок або власний варіант, що відповідає рядку). єдиний спосіб визначити тип корисної навантаження контейнера - це typedef 'value_type', який є частиною визначення класу контейнера.
Маріус

1

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.