Являются ли служебные классы, содержащие только статические члены, антипаттерном в C ++?


39

Вопрос « Куда мне помещать функции, не связанные с классом », вызвал некоторые споры о том, имеет ли смысл в C ++ объединять служебные функции в классе или они просто существуют как свободные функции в пространстве имен.

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

Изменится ли ситуация, если для набора служебных функций потребуются некоторые общие данные, например, кэш, который можно хранить в частном статическом поле?


Звучит как антипаттерн "функциональная декомпозиция".
user281377

7
Краткий ответ: вам просто не нужен класс, чтобы обернуть эти функции. Бесплатные функции гораздо лучше подходят для вашей задачи, чем встраивание их в какую-то псевдо-OO-конструкцию, которая является обходным путем, который вам нужен только в «чисто-OO» языках.
Крис говорит восстановить Монику

Ответы:


39

Я предполагаю ответить, что мы должны сравнить намерения как классов, так и пространств имен. Согласно Википедии:

Учебный класс

В объектно-ориентированном программировании класс - это конструкция, которая используется в качестве схемы для создания своих экземпляров - именуемых экземплярами класса, объектами класса, объектами экземпляра или просто объектами. Класс определяет составные члены, которые позволяют этим экземплярам класса иметь состояние и поведение. Члены поля данных (переменные-члены или переменные экземпляра) позволяют объекту класса поддерживать состояние. Другие типы членов, особенно методы, разрешают поведение объекта класса. Экземпляры класса имеют тип связанного класса.

Пространство имен

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

Теперь, чего вы пытаетесь достичь, помещая функции в класс (статически) или в пространство имен? Держу пари, что определение пространства имен лучше описывает ваше намерение - все, что вам нужно, это контейнер для ваших функций. Вам не нужны какие-либо функции, описанные в определении класса. Обратите внимание, что первые слова определения класса - « В объектно-ориентированном программировании », но в коллекции функций нет ничего объектно-ориентированного.

Вероятно, есть и технические причины, но, как кто-то из Java и пытается осмыслить мультипарадигмальный язык C ++, наиболее очевидный ответ для меня таков: потому что для этого не требуется ОО.


1
+1 за «нам не нужен ОО, чтобы достичь этого»
Ixrec

Я согласен с этим как не поклонник ОО, но я предполагаю, что есть несколько ситуаций, когда использование полностью статического класса является единственным решением, например, заменой пространства имен, так как вы не можете объявить вложенное пространство имен внутри класс.
Wael Boutglay

26

Я бы очень осторожно назвал это анти-паттерном. Пространства имен обычно предпочтительнее, но, поскольку нет шаблонов пространства имен, и пространства имен не могут быть переданы в качестве параметров шаблона, использование классов, не имеющих ничего, кроме статических членов, является довольно распространенным явлением.


3
Другие ответы затмили последнюю строку ОП. Пространства имен в значительной степени именуются областями, поэтому, если вам нужен контроль доступа, классы являются единственным доступным инструментом.
cmannett85

2
@ cbamber85, чтобы быть справедливым, я вставил эту строку только после прочтения первых двух ответов.
PersonalNexus

@PersonalNexus Ах, честно, я не поняла!
cmannett85

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

2
+1 Шаблоны - это действительно точка, в которой пространства имен все еще не имеют функций.
Крис говорит восстановить Монику

13

В тот день мне нужно было учиться на Фортране. К тому времени я знал другие императивные языки, поэтому решил, что смогу изучать фортран без особого изучения. Когда я сдал свою первую домашнюю работу, профессор вернул ее мне и попросил повторить ее: он сказал, что программы на языке Pascal, написанные в синтаксисе FORTRAN, не считаются действительными представлениями.

Схожая проблема здесь: использование статических классов для размещения служебных функций в C ++ - это идиома для C ++.

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

C ++, однако, предлагает пространства имен, другое средство для достижения той же цели, и он активно использует его в реализации своей стандартной библиотеки. Добавление дополнительного слоя статических классов не только не нужно, но и несколько нелогично для читателей без C # или фона Java. В некотором смысле вы вводите «перевод ссуды» на язык для чего-то, что может быть выражено естественным образом.

Когда ваши функции должны обмениваться данными, ситуация другая. Поскольку ваши функции больше не связаны , шаблон Singleton становится предпочтительным способом удовлетворения этого требования.


6
+1, кроме рекомендации синглетонов. Они не являются предпочтительными в C ++! Функции могут быть в классе, если они должны совместно использовать состояние, но классы не должны быть одиночными, если они действительно, действительно не должны быть. И они обычно не делают.
Maxpm

@ Maxpm Не поймите меня неправильно, синглтон предпочтительнее только глобальных статических переменных. Кроме этого, в этом нет ничего «предпочтительного».
dasblinkenlight

2
Есть хорошая статья « Одиночки: решение проблем, которых вы не знали с 1995 года» . В итоге говорится, что соединение хорошо известного экземпляра с самим классом излишне ограничивает вашу гибкость и ничего не дает. В большинстве случаев просто есть класс и где-то есть общий экземпляр, но не делайте его единичным.
Ян Худек

Я не уверен, что "иностранная идиома" является правильным термином. Это очень распространенная идиома. Я думаю, что довольно много команд программистов используют e2 из Stroustrup ...
Ник Кейли

10

Возможно, вам нужно спросить, почему вы хотите полностью статический класс?

Единственная причина, по которой я могу придумать, заключается в том, что другие языки (Java и C #), которые очень «все являются классом», требуют их. Эти языки вообще не могут создавать функции верхнего уровня, поэтому они придумали хитрость для их сохранения, и это была статическая функция-член. Это немного обходной путь, но C ++ не нуждается в таком, вы можете напрямую создавать совершенно новые независимые функции верхнего уровня.

Если вам нужны функции, которые работают с конкретным элементом данных, то имеет смысл объединить их в класс, содержащий данные, но затем они перестают быть функциями и начинают быть членами этого класса, которые работают с данными класса.

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


10
В самом деле - я бы сказал, что «класс со всеми статическими членами» в Java на самом деле является хаком, чтобы обойти ограничение Java.
Чарльз Сальвия

@CharlesSalvia: Я был вежливым :) У меня такое же плохое предчувствие, что main () тоже является частью Java-класса, хотя я понимаю, почему они это сделали.
gbjbaanb

На самом деле это, кажется, дает ответ на вопрос, почему вы хотите полностью статический класс. Или я вас неправильно понял? Например, мне нужно хранить переменную, значение которой может измениться, которая читается одним классом (который я намереваюсь сохранить в ПЗУ) и записывается другим классом (который будет в ОЗУ). Простого размещения в известном месте оперативной памяти недостаточно - оборачивая его (и его методы доступа) в класс, я могу точно настроить управление доступом. В значительной степени то, что вы описываете во втором абзаце.
iheanyi

5

По крайней мере, на первый взгляд статические методы класса кажутся неотличимыми от свободных функций в пространстве имен.

Другими словами, наличие класса вместо пространства имен не имеет преимуществ.

Почему при этом предпочтение отдается последнему?

Во-первых, это экономит ваше время при наборе текста static, хотя это, пожалуй, довольно незначительное преимущество.

Основным преимуществом является то, что это наименее мощный инструмент для работы. Классы могут использоваться для создания объектов, они могут использоваться как тип переменных или как аргументы шаблона. Ни одна из этих функций не нужна для вашей коллекции функций. Поэтому предпочтительнее использовать инструмент, который не обладает этими функциями, чтобы случайно не использовать класс.

Из этого следует, что использование пространства имен сразу же дает понять всем пользователям вашего кода, что это набор функций, а не план создания объектов.


5

... однако в нескольких комментариях говорится о том, что предпочтение следует отдавать свободным функциям, даже если предположить, что статические классы были анти-паттерном. Почему это так в C ++? По крайней мере, на первый взгляд статические методы класса кажутся неотличимыми от свободных функций в пространстве имен. Почему при этом предпочтение отдается последнему?

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

Изменится ли ситуация, если для набора служебных функций потребуются некоторые общие данные, например, кэш, который можно хранить в частном статическом поле?

На самом деле, нет. Первоначальная цель staticв C (и позже 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и classэс / 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);

Пока новый распределитель предоставляет функцию allocateand, releaseкоторая совместима, переключение на новый распределитель легко.

Этого нельзя достичь с помощью пространств имен.

Вам всегда нужны функции, чтобы быть частью класса? Нет.

Является ли использование статического класса антишаблоном? Это зависит от контекста.

Изменится ли ситуация, если для набора служебных функций потребуются некоторые общие данные, например, кэш, который можно хранить в частном статическом поле?

В этом случае то, что вы пытаетесь достичь, вероятно, лучше всего будет достигнуто с помощью объектно-ориентированного программирования.


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

1

Как сказал Джон Кармак:

«Иногда элегантная реализация - это просто функция. Не метод. Не класс. Не фреймворк. Просто функция».

Я думаю, что это в значительной степени подводит итог. Зачем вам делать это принудительно классом, если это явно не класс? C ++ обладает такой роскошью, что вы можете использовать функции; в Java все является методом. Тогда вам нужны вспомогательные классы, и их много.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.