Чи є утиліти класів, що не мають нічого, крім статичних членів, анти-шаблон у C ++?


39

Питання, куди я повинен розміщувати функції, не пов'язані з класом , викликав певну дискусію щодо того, чи є сенс у C ++ поєднувати утилітні функції в класі чи просто вони існують як вільні функції в просторі імен.

Я походжу з фону C #, де останнього варіанту не існує, і, природно, тенденція до використання статичних класів у маленькому коді C ++, який я пишу. Найвища відповідь на це питання, а також декілька коментарів, однак, говорять про те, що слід віддати перевагу вільним функціям, навіть припускаючи, що статичні класи були антидіаграмою. Чому так у C ++? Принаймні на поверхні статичні методи класу здаються невідмінними від вільних функцій у просторі імен. Чому, таким чином, перевага останньому?

Чи було б інакше, якби для збору функцій утиліти потрібні були спільні дані, наприклад кеш, який можна зберігати у приватному статичному полі?


Звучить трохи як антифункціональний "функціональний розпад".
користувач281377

7
Коротка відповідь: Вам просто не потрібен клас, щоб перетворити ці функції. Безкоштовні функції набагато чистіше підходять для вашого завдання, ніж їх розбивання на якусь псевдо-OO-конструкцію, яка є вирішенням, яке вам потрібно лише на "чисто OO" мовах.
Кріс каже, що повернеться до Моніки

Відповіді:


39

Я думаю, щоб відповісти, що ми повинні порівнювати наміри і класів, і просторів імен. За даними Вікіпедії:

Клас

В об'єктно-орієнтованому програмуванні клас - це конструкція, яка використовується як креслення для створення самих екземплярів - іменованих як екземпляри класу, об'єкти класу, об'єкти екземпляра або просто об'єкти. Клас визначає складових членів, які дозволяють цим екземплярам класу мати стан та поведінку. Члени поля даних (змінні члена або змінні екземпляра) дозволяють об’єкту класу підтримувати стан. Інші види членів, особливо методи, уможливлюють поведінку об'єкта класу. Екземпляри класу мають тип асоційованого класу.

Простір імен

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

Тепер, чого ви намагаєтеся досягти, розміщуючи функції в класі (статично) або просторі імен? Я б став на загрозу, щоб визначення простору імен краще описувало ваш намір - все, що вам потрібно, - це контейнер для ваших функцій. Вам не потрібна жодна з функцій, описаних у визначенні класу. Зауважимо, що перші слова визначення класу - " В об'єктно-орієнтованому програмуванні ", але щодо сукупності функцій немає нічого об'єктно-орієнтованого.

Ймовірно, є й технічні причини, але, оскільки хтось із Java і намагається обернути голову навколо мови багатопарадигми, що є C ++, найочевидніша відповідь для мене: Тому що нам не потрібен ОО для досягнення цього.


1
+1 за "нам не потрібен ОО для досягнення цього"
Ixrec

Я погоджуюся з цим як не-шанувальник OO, але я думаю, що є кілька ситуацій, коли використання All-статичного класу є єдиним рішенням, наприклад, заміною простору імен, оскільки ви не можете оголосити вкладений простір імен всередині клас.
Wael Boutglay

26

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


3
Інші відповіді змальовували останній рядок ОП. Простори імен в значній мірі називають сферами дії, тому якщо вам потрібен контроль доступу, класи є єдиним інструментом, який доступний.
cmannett85

2
@ cbamber85, щоб бути справедливим, я поставив цей рядок лише після прочитання перших двох відповідей.
PersonalNexus

@PersonalNexus Ну досить чесно, я не усвідомлював!
cmannett85

10
@ cbamber85: Це не зовсім так. У вас буде ще краща інкапсуляція, ввівши «приватні» функції у файл реалізації, так що користувачі не можуть бачити навіть приватну декларацію.
UncleBens

2
+1 Шаблони дійсно є пунктом, коли в просторах імен досі відсутні функції.
Кріс каже, що повернеться до Моніки

13

Ще в той день, коли мені потрібно було пройти курс FORTRAN. До того часу я знав інші імперативні мови, тому я подумав, що можу зробити FORTRAN без особливого вивчення. Коли я звернувся до свого першого домашнього завдання, професор повернув мені його і попросив повторити його: він сказав, що програми Паскаля, написані в синтаксисі FORTRAN, не вважаються достовірними.

Тут грає аналогічне питання: використання статичних класів для розміщення функцій утиліти в C ++ є іноземною ідіомою для C ++.

Як ви вже згадували у своєму запитанні, використання статичних класів для функцій утиліти в C # є необхідним: вільні функції просто не є варіантом у C #. Мова, необхідна для розробки схеми, яка дозволить програмістам визначати самостійно функціонування іншим способом, а саме - в межах статичних класів корисності. Це був хитрість Java, яка використовується слово в слово: наприклад, java.lang.Math і System.Math з .NET майже ізоморфні.

Однак C ++ пропонує простори імен, інший інструмент для досягнення тієї ж мети, і він активно використовує її при реалізації своєї стандартної бібліотеки. Додавання додаткового шару статичних класів є не лише зайвим, але й дещо контрсуєтним для читачів без C # або Java фону. У певному сенсі ви вводите в мову «переклад позики» на те, що можна висловити на самому собі.

Коли ваші функції потребують обміну даними, ситуація інша. Оскільки ваші функції більше не пов'язані між собою , шаблон Singleton стає кращим способом вирішення цієї вимоги.


6
+1, за винятком рекомендацій одинакових. Їм не віддають перевагу в C ++! Функції можуть бути в класі, якщо їм потрібно поділитися станом, але класи не повинні бути однотонними, якщо вони насправді не потребують цього. А вони зазвичай цього не роблять.
Maxpm

@Maxpm Не зрозумійте мене неправильно, синглтон віддають перевагу лише глобальним статичним змінним. Крім цього, в цьому немає нічого «переважного».
dasblinkenlight

2
Ось ця хороша стаття, Сінглтонс: Вирішення проблем, про які ви ніколи не знали ніколи з 1995 року . Узагальнений текст говорить про те, що з'єднання добре відомого екземпляра з самим класом зайво обмежує вашу гнучкість і нічого не дає. Більшу частину часу просто є клас та десь спільний екземпляр, але не робіть його однотонним.
Ян Худек

Я не впевнений, що "іноземна ідіома" - це правильний термін. Це дуже поширена ідіома. Я думаю, що досить багато команд програмування використовують е2 Stroustrup ...
Nick Keighley

10

Можливо, вам потрібно запитати, чому ви хочете загальностатичний клас?

Єдина причина, про яку я можу подумати, - це те, що для них потрібні інші мови (Java та C #), які є дуже "все це клас". Ці мови взагалі не можуть створювати функції вищого рівня, тому вони винайшли хитрість утримувати їх, і це була статична функція члена. Вони трохи вирішують, але C ++ такого не потребує, ви можете створювати абсолютно нові незалежні функції вищого рівня безпосередньо.

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

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


10
Справді - я б сказав, що "клас зі всіма статичними членами" на Java - це насправді хак, щоб обійти обмеження Java.
Чарльз Сальвія

@CharlesSalvia: Я був ввічливим :) У мене такі ж погані почуття щодо того, що main () бути частиною класу java теж, хоча я розумію, чому вони це зробили.
gbjbaanb

Це насправді дає відповідь на те, чому ви хочете загальностатичний клас. Або я вас неправильно розумію? Наприклад, мені потрібно утримувати змінну, значення якої може змінюватися, що читається одним класом (який я маю намір зберігати в ROM) і записується іншим класом (який буде в оперативній пам'яті). Просто розміщення місця у відомому місці оперативної пам’яті є недостатньою - загортання його (та його аксесуарів) у класі дозволяє мені точно налаштувати контроль доступу. Приблизно те, що ви описуєте у своєму другому абзаці.
iheanyi

5

Принаймні на поверхні статичні методи класу здаються невідмінними від вільних функцій у просторі імен.

Іншими словами, мати клас замість простору імен не має переваг.

Чому, таким чином, перевага останньому?

З одного боку, ви заощаджуєте staticвесь час вводити текст , хоча це, мабуть, досить незначна користь.

Основна перевага полягає в тому, що це найменш потужний інструмент для роботи. Класи можна використовувати для створення об’єктів, їх можна використовувати як тип змінних або як аргументи шаблону. Жодна з цих функцій не є функціями, необхідними для колекції функцій. Тому бажано використовувати інструмент, який не має цих функцій, щоб випадково не можна зловживати класом.

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


5

... однак декілька коментарів говорять, що слід віддавати перевагу вільним функціям, навіть припускаючи, що статичні класи були антидіаграмою. Чому так у C ++? Принаймні на поверхні статичні методи класу здаються невідмінними від вільних функцій у просторі імен. Чому, таким чином, перевага останньому?

Загальностатичний клас виконає роботу, але це як те, щоб загнати 53-футову напіввантажівку до продуктового магазину для фішок і сальси, коли чотиридверний седан зробить (тобто, це надмірно). Заняття мають невелику кількість додаткових накладних витрат, і їх існування може створити на когось таке враження, що подання заявки може бути хорошою ідеєю. Безкоштовні функції, пропоновані C ++ (і C, де всі функції вільні), цього не роблять; вони просто функції і більше нічого.

Чи було б інакше, якби для збору функцій утиліти потрібні були спільні дані, наприклад кеш, який можна зберігати у приватному статичному полі?

Не зовсім. Первісна мета staticC (і пізніше C ++) - запропонувати стійке сховище, яке можна використовувати на будь-якому рівні сфери:

int counter() {
    static int value = 0;
    value++;
}

Ви можете вивести область на рівень файлу, що робить його видимим для всіх вужчих областей, але не за межами файлу:

static int value = 0;

int up() { value++; }
int down() { value--; }

Приватний член статичного класу в C ++ служить тій самій цілі, але обмежується сферою дії класу. Метод, використаний у counter()наведеному вище прикладі, також працює всередині методів C ++, і я насправді рекомендую це робити, якщо змінній не потрібно бачити весь клас.


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

Якщо вони діляться даними, чи можуть вони бути кращими як клас без статичних методів?
Нік Кейлі

@ NickKeighley Це залежить від того, хто виграє дискусію щодо того, чи краще створити один екземпляр і передати його навколо всього, що йому потрібно, або просто зробити його статичним у своєму класі.
Blrfl

1

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


1

Більшість дискурсів з цієї теми має сенс, хоча є щось дуже принципове в C ++, що робить namespacesі classes / structs дуже різними.

Статичні класи (класи, де всі члени статичні, а клас ніколи не буде ідентифікований) самі є об'єктами. Вони не просто namespaceмістять функції.

Метапрограмування шаблонів дозволяє використовувати статичний клас як об'єкт часу компіляції.

Врахуйте це:

template<typename allocator_type> class allocator
{
public:
    inline static void* allocate(size_t size)
    {
        return allocator_type::template allocate(size);
    }
    inline static void release(void* p)
    {
        allocator_type::template release(p);
    }
};

Для його використання нам потрібні функції, що містяться всередині класу. Тут не працюватиме простір імен. Поміркуйте:

class mallocator
{
    inline static void* allocate(size_t size)
    {
        return std::malloc(size);
    }
    inline static void release(void* p)
    {
        return std::free(p);
    }
};

Тепер використовувати його:

using my_allocator = allocator<mallocator>;

void* p = my_allocator::allocate(1024);
...
my_allocator::release(p);

Поки новий алокатор виставляє allocateі releaseсумісну функцію, перехід на новий розподільник є простим.

Цього неможливо досягти просторами імен.

Вам завжди потрібні функції, щоб бути частиною класу? Ні.

Чи використання статичного класу є антитілом? Це залежить від контексту.

Чи було б інакше, якби для збору функцій утиліти потрібні були спільні дані, наприклад кеш, який можна зберігати у приватному статичному полі?

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


Використання ключового слова inline тут зайве, оскільки методи, визначені у визначенні класу, неявно вбудовані. Див. En.cppreference.com/w/cpp/language/inline .
Тревор

1

Як чудово сказав Джон Кармак:

"Іноді елегантна реалізація - це лише функція. Не метод. Не клас. Не рамка. Просто функція."

Я думаю, що це дуже підсумовує це. Чому б ти зробив це насильно класом, якщо він явно не клас? C ++ має розкіш, що ви можете реально використовувати функції; на Java все - метод. Тоді вам знадобляться корисні класи, і їх багато.

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