Categories: C/C++

singleton-class/

Хочется думать, что объяснять, что такое класс-синглтон, или он же паттерн «одиночка», читателям данного блога не требуется. Если вдруг это не так, вам поможет, например, соответствующая статья на Википедии . Мне же хотелось бы поделиться кое-какими мыслями касательно неоднозначного отношения к данному паттерну, следует ли его вообще использовать, и если да, то когда.

Напомню, что, по мнению некоторых людей, класс-синглтон в сущности представляет собой глобальную переменную. И этот факт, безусловно, подмечен весьма верно. Вред же от использования глобальных переменных всем давно известен. В частности, в используемом их коде появляется скрытое от пользователя, неявное, состояние. И этого, безусловно, никто не хочет. Подробности, если память не изменяет, красочно описываются в книге Совершенный код Стива Макконнелла, поэтому здесь на этом моменте подробнее я останавливаться не буду. Из всего этого делается вывод, что синглтоны — это антипаттерн и пользоваться ими не нужно вообще никогда.

Однако недавно мне попался интересный контр-пример. Ситуация примерно следующая. Есть класс HttpServer с очевидным назначением. На каждое новое соединение он создает новый поток (thread) и экземпляр класса HttpWorker, обслуживающий соединение на созданном потоке. Для парсинга HTTP-заголовков использовались регулярные выражения и библиотека libpcre . Предвидя многочисленные вопросы на тему «зачем писать свой HTTP-сервер», сразу поясню, что делалось все это just for fun. (Думается, что работа не пропала даром, раз она уже породила больше одного поста.)

Так вот, в наивной реализации класс HttpWorker постоянно компилировал и освобождал регулярные выражения. Что, понятное дело, как-то расточительно, учитывая типично небольшое время жизни класса и неизменяемость регулярных выражений. Хотелось прикрутить кэш скомпилированных регулярных выражений, который использовался бы всеми экземплярами класса. Сказано — сделано.

Файл RegexCache.h:

#pragma once

#include <pcre.h>

class RegexCache {
public :
static const RegexCache & getInstance ( ) ;

RegexCache ( RegexCache const & ) = delete ;
void operator = ( RegexCache const & ) = delete ;

~RegexCache ( ) ;
const pcre * getHttpRequestPattern ( ) const ;
const pcre * getHttpHeaderPattern ( ) const ;

private :
RegexCache ( ) ; /* keep the constructor private! */
pcre * _httpRequestPattern ;
pcre * _httpHeaderPattern ;
} ;

Файл RegexCache.cpp:

#include <RegexCache.h>
#include <pcre.h>
#include <stdexcept>

const RegexCache & RegexCache :: getInstance ( ) {
static RegexCache cache ;
return cache ;
}

RegexCache :: RegexCache ( ) {
/*
* Код пропущен, так как компиляция регулярных выражений
* отношения к делу не имеет.
*/

}

RegexCache :: ~RegexCache ( ) {
if ( _httpRequestPattern ! = nullptr )
pcre_free ( _httpRequestPattern ) ;

if ( _httpHeaderPattern ! = nullptr )
pcre_free ( _httpHeaderPattern ) ;
}

const pcre * RegexCache :: getHttpRequestPattern ( ) const {
return _httpRequestPattern ;
}

const pcre * RegexCache :: getHttpHeaderPattern ( ) const {
return _httpHeaderPattern ;
}

Соответственно, вместо постоянной компиляции и освобождения регулярных выражений, код стал просто брать их из кэша:

auto req_re = RegexCache :: getInstance ( ) . getHttpRequestPattern ( ) ;

Так почему же здесь класс-синглтон не создает никаких проблем? А потому что в данном конкретном случае класс-синглтон не является глобальной переменной . В сущности он является совершенно безвредной константой , пусть и с замороченной инициализацией. В более общем случае константность синглтона может изменяться. Например, синглтон может быть переменной сразу после запуска приложения, после чего становиться константой. Не думаю, что это существенно изменит ситуацию.

Кто-то возразит, что можно обойтись и без синглтона, просто объявив в классе HttpWorker статический класс RegexCache. Но в этом случае мы потеряем гарантию того, что RegexCache существует ровно в одном экземпляре. В частности, спустя пару лет приложение может быть переписано, в результате чего по ошибке создастся второй RegexCache. Эта гарантия эффективно ничего не стоит, и лишать себя ее чисто из-за перфекционизма мне видится нецелесообразным.

Вывод — синглтоны могут быть полезны. Просто не нужно лепить их на каждый чих. Что же до ускорения кода за счет кэширования, оно составило плюс 5-6% запросов в секунду. На мой взгляд, для условного получаса работы это очень даже неплохо. Соответствующий код можно найти в данном репозитории . По крайней мере, он там точно есть в районе коммита 87c80aaf. В будущем ситуация может поменяться.

А можете ли вы описать какие-то еще сценарии, когда стоит использовать синглтоны?

admin

Share
Published by
admin
Tags: C/C++

Recent Posts

vim-commands/

Самое главное — побороть боязнь белого листа. Я всегда говорю это себе, когда нужно начать…

1 месяц ago

firefox-thunderbird-en-ru-dict/

По не вполне ясным причинам, Firefox умеет проверять орфографию либо только в русских, либо только…

1 месяц ago

perl-hacks/

Около месяца собирал разные «хаки» на языке программирования Perl. Эта подборка наглядно демонстрирует, как в…

1 месяц ago

perl-cy-check/

C недавних пор я стал увлекаться SEO. Порой передо мной встает задача быстро проверить индекс…

1 месяц ago

which-cms-perl/

Недавно написал несколько скриптов, позволяющих автоматически определять, какая CMS (Content Management System, система управления контентом)…

1 месяц ago

smtp-descr/

Я так подозреваю, что среди вас найдется те, кто скажет, что этот пост боян и…

1 месяц ago