Синглтон и время жизни объекта

    Эта статья является продолжением моей первой статьи “Использование паттерна синглтон” [0]. Сначала я хотел все, что связано со временем жизни, изложить в этой статье, но объем материала оказался велик, поэтому решил разбить ее на несколько частей. Это — продолжение целого цикла статей про использование различных шаблонов и методик. Данная статья посвящена времени жизни и развитию использования синглтона. Перед прочтением второй статьи настоятельно рекомендуется ознакомиться с моей первой статьей [0].

    В предыдущей статье была использована следующая реализация для синглтона:
    template<typename T>
    T& single()
    {
        static T t;
        return t;
    }
    


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

    Так уж получилось, что в языке C++ отсутствует сборщик мусора, поэтому необходимо следить за созданием и уничтожением объекта. И хотя эта проблема уже давно известна и даже понятны методы как ее решать, подобного рода ошибки не редкий гость в наших программах. В целом можно выделить следующие виды ошибок, которые делают программисты:
    1. Использование памяти при не созданном объекте.
    2. Использование памяти уже удаленного объекта.
    3. Неосвобождение памяти, занимаемой объектом.

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

    На примере синглтона можно с легкостью показать, как делаются такие ошибки. Открываем статью в Википедии [1] и находим реализацию для C++:
    class OnlyOne
    {
    public:
        static OnlyOne* Instance()
        {
            if(theSingleInstance==0)
                theSingleInstance=new OnlyOne;
            return theSingleInstance;
        }
    private:
        static OnlyOne* theSingleInstance;
        OnlyOne(){};
    };
    OnlyOne* OnlyOne::theSingleInstance=0;
    

    Видно, что для синглтона память выделяется, однако по какой-то причине не освобождается. Можно, конечно, сказать, что для синглтона это не является серьезной проблемой, так как его время жизни совпадает с временем работы программы. Однако тут есть ряд но:
    1. Программы по поиску утечек памяти будут все время показывать эти утечки для синглтонов.
    2. Синглтоном может быть достаточно сложный объект, обслуживающий открытый конфигурационный файл, связь с базой данных и проч. Неправильное уничтожение таких объектов может вызывать проблемы.
    3. Все начинается с малого: сначала не следим за памятью для синглтонов, а потом и для остальных объектов.
    4. И главные вопрос: зачем делать неправильно, если можно сделать правильно?

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

    Анализируя исходный код различных программных продуктов я для себя выделил еще 2 важных правила:
    1. Не использовать new.
    2. Не использовать delete.

    Тут стоит немного пояснить, что я имею в виду. Понятно, что где-то все равно будет вызываться new и delete. Речь про то, что это должно находиться строго в одном месте, лучше в одном классе, чтобы это не распылять по программе. Тогда, при правильной организации такого класса, не надо будет следить за временем жизни объектов. И я сразу скажу, что это возможно! Стоит сразу оговориться, что такой подход мне нигде не встречался. Так что будем своего рода первооткрывателями.

    Умные указатели


    К счастью, в C++ есть замечательное средство, которое называется «умный указатель». Их умность заключается в том, что, хотя они и ведут себя как обычные указатели, при этом контролируют время жизни объектов. Для этого они используют счетчик, который самостоятельно подсчитывает количество ссылок на объект. При достижении счетчиком нуля объект автоматически уничтожается. Будем использовать умный указатель из стандартной библиотеки std::shared_ptr заголовочного файла memory. Стоит отметить, что такой класс доступен для современных компиляторов, которые поддерживают стандарт C++0x. Для тех, кто использует старый компилятор, можно использовать boost::shared_ptr. Интерфейсы у них абсолютно идентичны.

    Возложим на наш класс An следующие обязанности:
    1. Контроль времени жизни объектов, используя умные указатели.
    2. Создание экземпляров, в том числе и производных классов, не используя операторов new в вызывающем коде.


    Этим условиям удовлетворяет следующая реализация:
    template<typename T>
    struct An
    {
        template<typename U>
        friend struct An;
    
        An()                              {}
    
        template<typename U>
        An(const An<U>& a) : data(a.data) {}
    
        template<typename U>
        An(An<U>&& a) : data(std::move(a.data)) {}
    
        T* operator->()                   { return get0(); }
        const T* operator->() const       { return get0(); }
        bool isEmpty() const              { return !data; }
        void clear()                      { data.reset(); }
        void init()                       { if (!data) reinit(); }
        void reinit()                     { anFill(*this); }
        
        T& create()                       { return create<T>(); }
    
        template<typename U>
        U& create()                       { U* u = new U; data.reset(u); return *u; }
        
        template<typename U>
        void produce(U&& u)               { anProduce(*this, u); }
    
        template<typename U>
        void copy(const An<U>& a)         { data.reset(new U(*a.data)); }
    
    private:
        T* get0() const
        {
            const_cast<An*>(this)->init();
            return data.get();
        }
    
        std::shared_ptr<T> data;
    };
    


    Стоит остановиться поподробнее на предложенной реализации:
    1. Конструктор использует move-семантику [6] из C++0x стандарта для увеличения быстродействия при копировании.
    2. Метод create создает объект нужного класса, по умолчанию создается объект класса T.
    3. Метод produce создает объект в зависимости от принимаемого значения. Назначение этого метода будет описано позднее.
    4. Метод copy производит глубокое копирование класса. Стоит отметить, что для копирования в качестве параметра необходимо указывать тип реального экземпляра класса, базовый тип не подходит.


    При этом синглтон перепишется в следующем виде:
    template<typename T>
    struct AnAutoCreate : An<T>
    {
        AnAutoCreate()     { create(); }
    };
    
    template<typename T>
    T& single()
    {
        static T t;
        return t;
    }
    
    template<typename T>
    An<T> anSingle()
    {
        return single<AnAutoCreate<T>>();
    }
    


    Вспомогательные макросы будут такими:
    #define PROTO_IFACE(D_iface, D_an)    \
        template<> void anFill<D_iface>(An<D_iface>& D_an)
    
    #define DECLARE_IMPL(D_iface)    \
        PROTO_IFACE(D_iface, a);
    
    #define BIND_TO_IMPL(D_iface, D_impl)    \
        PROTO_IFACE(D_iface, a) { a.create<D_impl>(); }
    
    #define BIND_TO_SELF(D_impl)    \
        BIND_TO_IMPL(D_impl, D_impl)
    
    #define BIND_TO_IMPL_SINGLE(D_iface, D_impl)    \
        PROTO_IFACE(D_iface, a) { a = anSingle<D_impl>(); }
    
    #define BIND_TO_SELF_SINGLE(D_impl)    \
        BIND_TO_IMPL_SINGLE(D_impl, D_impl)
    
    #define BIND_TO_IFACE(D_iface, D_ifaceFrom)    \
        PROTO_IFACE(D_iface, a) { anFill<D_ifaceFrom>(a); }
    
    #define BIND_TO_PROTOTYPE(D_iface, D_prototype)    \
        PROTO_IFACE(D_iface, a) { a.copy(anSingle<D_prototype>()); }
    


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

    Использование синглтона


    Теперь рассмотрим использование описанного класса для реализации синглтона:
    // header file
    struct X
    {
        X()            { x = 1; }
        
        int x;
    };
    // декларация заливки реализации
    DECLARE_IMPL(X)
    
    // cpp file
    struct Y : X
    {
        Y()            { x = 2; }
            
        int y;
    };
    // связывание декларации X и реализации Y используя синглтон
    BIND_TO_IMPL_SINGLE(X, Y)
    


    Теперь это можно использовать следующим образом:
    An<X> x;
    std::cout << x->x << std::endl;
    


    Что на экране даст цифру 2, т.к. для реализации использовался класс Y.

    Контроль времени жизни


    Рассмотрим теперь пример, который показывает важность использования умных указателей для синглтонов. Для этого разберем следующий код:
    struct A
    {
        A()  { std::cout << "A" << std::endl; a =  1; }
        ~A() { std::cout << "~A" << std::endl; a = -1; }
        
        int a;
    };
    
    struct B
    {
        B()  { std::cout << "B" << std::endl; }
        ~B() { std::cout << "~B" << std::endl; out(); }
        
        void out() { std::cout << single<A>().a << std::endl; }
    };
    


    Теперь посмотрим, что выведется на экран при таком вызове функции out:
    single<B>().out();
    
    // вывод на экран
    B
    A
    1
    ~A
    ~B
    -1
    

    Разберемся, что здесь происходит. В самом начале мы говорим, что хотим реализацию класса B, взятую из синглтона, поэтому создается класс B. Затем вызываем фукнцию out, которая берет реализацию класса A из синглтона и берет значение a. Величина a задается в конструкторе A, поэтому на экране появится цифра 1. Теперь программа заканчивает свою работу. Объекты начинают уничтожатся в обратной последовательности, т.е. сначала разрушается класс A, созданный последним, а потом разрушается класс B. При разрушении класса B мы снова зовем фукнцию out из синлтона, но т.к. объект A уже разрушен, то на экране мы видим надпись -1. Вообще говоря, программа могла и рухнуть, т.к. мы используем память уже разрушенного объекта. Таким образом, данная реализация показывает, что без контроля времени жизни программа может благополучно упасть при завершении.

    Давайте теперь посмотрим, как можно сделать то же самое, но с контролем времени жизни объектов. Для этого будем использовать наш класс An:
    struct A
    {
        A()    { std::cout << "A" << std::endl; a = 1; }
        ~A()   { std::cout << "~A" << std::endl; a = -1; }
        
        int a;
    };
    // связывание декларации A с собственной реализацией используя синглтон
    BIND_TO_SELF_SINGLE(A)
    
    struct B
    {
        An<A> a;
        
        B()    { std::cout << "B" << std::endl; }
        ~B()   { std::cout << "~B" << std::endl; out(); }
        
        void out() { std::cout << a->a << std::endl; }
    };
    // связывание декларации B с собственной реализацией используя синглтон
    BIND_TO_SELF_SINGLE(B)
    
    // код
    An<B> b;
    b->out();
    


    Данный код практически ничем не отличается от предыдущего, за исключением следующих важных деталей:
    1. Объекты A и B используют класс An для синглтонов.
    2. Класс B явно декларирует зависимость от класса A, используя соответствующий публичный член класса (подробнее об этом подходе можно узнать из предыдущей статьи).


    Посмотрим, что теперь выведется на экран:
    B
    A
    1
    ~B
    1
    ~A
    

    Как видно, теперь мы продлили время жизни класса A и изменили последовательность уничтожения объектов. Отсутствие значения -1 говорит о том, что объект существовал во время доступа к его данным.

    Итого


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

    P.S.


    Многие спрашивают, а в чем, собственно, смысл? Почему нельзя просто сделать синглтон? Зачем использовать какие-то дополнительные конструкции, которые ясности не добавляют, а лишь усложняют код. В принципе, при внимательном прочтении первой статьи [0] уже можно понять, что данный подход более гибок и устраняет ряд существенных недостатков синглтона. В следующей статье будет отчетливо понятно, зачем я это так расписывал, т.к. в ней речь уже будет идти не только о синглтоне. А через статью будет вообще понятно, что синглтон тут абсолютно не при чем. Все, что я пытаюсь показать — это использование Dependency inversion principle [4] (см. также The Principles of OOD [5]). Собственно, именно после того, как я увидел впервые этот подход на Java, мне стало обидно, что в C++ это слабо используют (в принципе, есть фреймворки, которые предоставляют подобный функционал, но хотелось бы чего-то более легковесного и практичного). Приведенная реализация — это лишь маленький шажок в этом направлении, который уже приносит огромную пользу.

    Также хотелось бы отметить еще несколько вещей, отличающих приведенную реализацию от классического синглтона (вообще говоря, это следствия, но они важны):
    1. Класс, описывающий синглтон, можно использовать в нескольких экземплярах без каких-либо ограничений.
    2. Синглтон заливается неявно посредством функции anFill, которая контролирует количество экземпляров объекта, при этом можно использовать конкретную реализацию вместо синглтона при необходимости (показано в первой статье [0]).
    3. Есть четкое разделение: интерфейс класса, реализация, связь между интерфейсом и реализацией. Каждый решает только свою задачу.
    4. Явное описание зависимостей от синглтонов, включение этой зависимости в контракт класса.


    Update


    Почитав комментарии я понял, что есть некоторые моменты, которые стоит прояснить, т.к. многие не знакомы с принципом обращения зависимостей (dependency inversion principle, DIP или inversion of control, IoC). Рассмотрим следующий пример: у нас есть база данных, в которой содержится необходимая нам информация, например список пользователей:
    struct IDatabase
    {
        virtual ~IDatabase() {}
    
        virtual void beginTransaction() = 0;
        virtual void commit() = 0;
        ...
    };
    

    У нас есть класс, который выдает нужную нам информацию, в том числе и необходимого пользователя:
    struct UserManager
    {
        An<IDatabase> aDatabase;
        
        User getUser(int userId)
        {
            aDatabase->beginTransaction();
            ...
        }
    };
    

    Здесь мы создаем член aDatabase, который говорит о том, что ему необходима некая база данных. Ему не важно знать, что это будет за база данных, ему не нужно знать, кто и когда это будет заполнять/заливать. Но класс UserManager знает, что ему туда зальют то, что нужно. Все, что он говорит, это: «дайте мне нужную реализацию, я не знаю какую, и я сделаю все, что вам нужно от этой базы данных, например, предоставлю необходимую информацию о пользователе из этой базы данных».

    Теперь мы делаем хитрый трюк. Так как у нас есть одна лишь база данных, которая содержит всю нашу информацию, то мы говорим: ок, раз есть только одна база данных, давайте сделаем синглтон, и чтобы не париться каждый раз в заливке реализации, сделаем так, чтобы синглтон сам заливался:
    struct MyDatabase : IDatabase
    {
        virtual void beginTransaction();
        ...
    };
    BIND_TO_IMPL_SINGLE(IDatabase, MyDatabase)
    

    Т.е. мы создаем реализацию MyDatabase и говорим, что для синглтона будем использовать именно ее, используя макрос BIND_TO_IMPL_SINGLE. Тогда следующий код автоматически будет использовать MyDatabase:
    UserManager manager;
    User user = manager.getUser(userId);
    

    С течением времени оказалось, что у нас есть еще одна база данных, в которой тоже есть пользователи, но, скажем, для другой организации:
    struct AnotherDatabase : IDatabase
    {
        ...
    };
    

    Мы конечно же хотим использовать наш UserManager, но уже с другой базой данных. Нет проблем:
    UserManager manager;
    manager.aDatabase = anSingle<AnotherDatabase>();
    User user = manager.getUser(userId);
    

    И как по волшебству, теперь мы берем пользователя из другой базы данных! Это достаточно грубый пример, но он отчетливо показывает принцип обращения зависимостей: это когда объекту UserManager заливают реализацию IDatabase вместо традиционного подхода, когда UserManager сам ищет себе необходимую реализацию. В рассмотренной статье используется этот принцип, при этом синглтон для реализации берется как частный случай.

    Литература


    [0] Использование паттерна синглтон
    [1] Википедия: синглтон
    [2] Inside C++: синглтон
    [3] Порождающие шаблоны: Одиночка (Singleton)
    [4] Dependency inversion principle
    [5] The Principles of OOD
    [6] Wikipedia: Rvalue reference and move semantics

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 61

      +5
      Зачем использовать макросы в плюсовом коде?
        +3
        Макросы я использовал, начиная с первой статьи. В принципе, можно обойтись и без них. Однако, в данном случае макросы являются чем-то вроде декларации и являются как бы частью интерфейса, дополняют класс в описательном стиле. Я не склонен думать, что макросы — это зло. Просто их надо правильно использовать.

        Взять, например, макрос BIND_TO_SELF_SINGLE. Он говорит о том, что синглтон связывается сам с собой. Здесь мне не нужно знать, как этот макрос устроен. Мне нужно знать, что в результате получится. В последствии я могу заменить реализацию макроса (если потребуется), а все пользователи даже и не заметят этого. Таким образом, макросы выступают в роли интерфейса, как это ни странно.
          +2
          Макросы «очень даже добро» когда они повышают читабельность кода, как например вот тут:
          #define PROTO_IFACE(D_iface, D_an) \
          template<> void anFill<D_iface>(An<D_iface>& D_an)


          Как верно сказал gridem: Однако, в данном случае макросы являются чем-то вроде декларации и являются как бы частью интерфейса, дополняют класс в описательном стиле.
          т.е. скрывают реализацию там где она явно не нужна.
            0
            oops: т.е. скрывают реализацию там, где её знать не обязательно.
              0
              и крайне понижают возможность корректной отладки такого чудо-макро-кода… что мешает заменить макрос inline-функцией?
                +1
                И как же определение темплейта заменить инлайн-функцией?
                  0
                  параметризованной инлайн-функцией…
                    0
                    Хорошо, на примере PROTO_IFACE, что там куда заменять инлайн-функциями?
                    Темплейты вообще-то по определению инлайн. Но не всегда хочется писать громоздкую непонятную конструкцию с угловыми скобками и дублированием типов, typedef в этом случае не поможет, а макросы вполне даже.
                    Конечно, C-style макросы это тихий ужас и привет отладке, но в си++ другого не дано (например, доступа к AST).
                      0
                      мой комментарий относился скорее не конкретно к приведенному примеру, а к комментариям относительно того, что макросы повышают читабельность кода, являются как бы частью интерфейса и скрывают реализацию там где она явно не нужна…

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

                      по поводу громоздкой непонятной конструкции с угловыми скобками и дублированием типов; Вы правда считаете, что сокрытие такой конструкции за волшебным макросом повысит читабельность и понимание кода? от дублирования длинных шаблонных типов хорошо помогает typedef, все остальное должно быть явно написано без каких-либо игр в прятки с макросами…

                      относительно «в си++ другого не дано» я вообще не понял… еще раз в с++ есть все необходимые языковые средства для замены макросов…

                      на примере PROTO_IFACE нужно оставить явную специализацию шаблона, а не городить макро-огород, часто используемые типа а ля An<D_iface> заменить коротким синонимом при помощи typedef…
                        +1
                        Да что же вы так любите недоговаривать? Когда темлейты не инлайн? Естественно, под инлайн мы понимаем опцию линковки, определяемую ключевым словом inline, а не то, что код функций будет заинлайнен компилятором, так ведь?

                        Да, я считаю, что правильно используемые макросы упрощают понимание и написание кода (но усложняют отладку). Интерфейс верхнего уровня — это «что делать», а не «как делать», и простой синтаксис, поэтому лично я бы предпочёл написать PROTO_IFACE а не специализировать шаблоны.

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

                        Заменить An<D_iface> typedef'ом не сильно поможет, т.к. останется ещё много вещей, которые придётся продублировать. И опять же, это будут детали имлементации, «как делать», а не «что делать». Вот если бы можно было заменить макросы из статьи на что-нибудь вида BindImpl<IDatabase, MyDatabase>, я был бы согласен, но с декларациями это не работает.

                        Вообще, вы писали шаблонный код, использующий другие шаблонные классы, и т.п., например биндинги как в статье? Видели исходники boost? И после этого серьёзно считаете, что мета-программирование в си++ всегда лучше, чем макросы? Я считаю, что для полноценного мета-программирования нужен полноценный макро-язык, иначе выходит нечитаемый ужас как в практически любой шаблонной магии си++.
                          0
                          А что же Вы так любите задавать глупые вопросы? :) Когда темлейты не инлайн может зависеть от многих факторов, начиная от реализации конкретного компилятора, его настроек и заканчивая кодом конкрентного темплейта… и я не пойму с чего Вы вдруг приплели сюда линковку, если речь пока идет о препроцессинге и компиляции?

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

                          Вы не поверите, но не только видел исходники stl, boost, loki, mfc, atl, wtl, но еще и написал не мало кода с их использованием, и еще не меньше дебажил чужого кода используещего эти самые библиотеки, а вот судя по Вам, раз уж мы перешли на личности, не хватает опыта участия в крупных промышленных решениях, которые разрабатываются несколько лет, несколькими поколениями разработчиков… так вот отлаживаться и искать дефекты в коде напичканном макросами такого рода проектов занятие скажем так на любителя… И да, вот именно после этого, я категорически считаю, что мета-программирование в си++ всегда лучше, чем макросы.
                            0
                            Ого, я вообще-то и не пытался переходить на личности, просто спросил, из практики вы делаете выводы или просто так, сорри если чем обидел.

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

                            Вы, видимо, под инлайном подразумеваете то, что код функции будет заинлайнен. Так в этом случае для меня нет никаких откровений, ясно дело всё зависит от компилятора, может и не-inline заинлайниться, а может и вообще link-time «настоящая» функция из другого юнита. Зачем об этом вообще говорить в этом обсуждении?

                            По-моему, на личности как раз вы переходите. Я-то как раз себя не выставляю умнее других, а пытаюсь обсуждать конкретные вопросы. Классики вроде Страуструпа редко утверждают что-нибудь в духе «макросы не нужны», а говорят о конкретных ситуациях.

                            В том-то и дело, что макросами не должно быть «напичкано», но это не значит что их вообще нельзя применять там, где уместно. Ещё раз, я бы предпочёл что-то статически типизированное макросам, но не всегда это возможно, приходится жить с тем, что есть.
                              0
                              Да нет проблем, на обиженных воду возят… :) меня просто смутил ваш напор…

                              Я честно говоря не совсем понял что под inline подразумеваете Вы, можно просто пример небольшой?

                              Относительно переходя на личности, прошу прощения если не так Вас понял, видимо это у Вас просто манера общения такая… а классики вроде Страуструпа и не только, как раз прямым текстом и говорят, что макросы это зло.

                              В том-то и дело, что мест таких при программировании на с++ в стиле с++ нет, так же как и нет места в с++ «голым» указателям например…
                                0
                                Ну есть такое, я своё мнение всегда отстаиваю достаточно прямо :) Просто пока что так и не увидел серьёзных аргументов против. Вообще-то я тоже против макросов в целом, но считаю что иногда (довольно редко) с ними лучше чем без них. Макросы — зло, но есть и другие «злы», например копи-паст, и имхо иногда первое оказывается меньшим злом…

                                Пример с инлайном:
                                // test1.h
                                void f() {}

                                // test2.h
                                inline void f() {}

                                // test3.h
                                template<class T>
                                void f() {}


                                Теперь, если мы эти хидеры инклюдим из двух .cpp, то в первом случае естественно получаем ошибку линковки из-за двойного определения. Во втором — этого не будет, т.к. линковка статическая (аналогично static void f() {}, но static неприменим к членам классов, и там остаётся только inline). В третьем варианте тоже проблем не будет, т.к. по стандарту темплейты не приводят к множественному определению, то есть получается тот же inline (или static его называть?..). Вообще, в си++ устроили какую-то мешанину со static, inline и компанией со своими стараниями не добавлять ключевиков, теперь чёрт ногу сломит разобраться что означает что…

                                Пример с возвращением указателей из функций, кстати, знаковый — вроде все согласны, что в си++ это нехорошо, но при этом не помню чтобы я видел в книжках чтобы те же самые фабрики возвращали не голые указатели. То есть, вроде как нехорошо, но всем пофиг :) Или можете подсказать литературу где это не так?
                                  0
                                  очень странный пример честно говоря, так как во-первых все хидеры в с/с++ необходимо защищать от повторного включения соответствующими методами, тогда у Вас не будет проблем и в первом варианте… во-вторых ключевое слово static в данном случае говорит компилятору, что область видимости переменной ограничена модулем, в котором она объявлена, насколько я помню… термин статическая линковка первый раз слышу, но понятно о чем Вы, есть понятия внутренней и внешней линковки, которые завязаны как раз на область видимости переменной, тут Вы правы, static в данном примере используется для внутренней линковки… ну и в 3х, первый раз вижу чтобы для такого поведения использовали ключевое слово inline, так как оно предназначено для указания того, что вы хотите чтобы тело функции было вставлено в код вместо ее вызова, собственно из-за этого у нас и возникла легкая неразбериха…

                                  что происходит в вашем примере:
                                  1 — вы все верно описали, но тут проблема защиты от повтороного включения, которой нет
                                  2 — линковщик не ругается по причине того, что он думает, что Вы хотите использовать код функции вместо ее имени, соотвественно проблем с дублированием имен нет… для поведения, которое Вы хотели необходимо использовать ключевое слово static…
                                  3 — с темплейтами ситуация несколько похоже на предыдущую, но линковка будет внешняя

                                  так что получается, что у Вас небольшая неразбериха, а не в с++ :)
                                    0
                                    Там дело не в повторном включении, а во включении из разных юнитов (.cpp), так что include guards не помогут — проблема именно при линковке.

                                    «статическая линковка» это я от балды так называю (видимо, это внутренняя линковка как вы говорите). Как я писал выше, ключевое слово static тут сработает, но не сработает для членов классов, например:

                                    // .h
                                    class X
                                    {
                                    void f();
                                    }

                                    inline void X::f() {...} // cannot be "static"


                                    Получается,
                                    1 — проблема именно во внутренней/внешней линковке, а не в защите от повторного включения
                                    2 — линковщик ругается, т.к. из двух юнитов (объектных файлов) экспортирована функция с одной сигнатурой
                                    3 — у темплейтов не бывает настоящей внешней линковки, шаблонную функцию снаружи дёрнуть нельзя. Кроме того, компилятор вообще не обязан экспортировать шаблонную функцию. В случае если он всё же это сделает, то множественные определения будут схлопнуты в одно. Если не вдаваться в эти детали, то можно сказать, что темплейты фактически не экспортируются (что и есть моё изначальное утверждение)

                                    У меня-то как раз «разбериха» ;)
                                      0
                                      1 — все верно, дело во включении из разных юнитов, только кто в здравом уме и для чего это будет делать, если заранее известно, что такой код не «соберется» из-за повтороного определения из хидера без защиты от повторного включения???

                                      2 — я же Вам написал, что inline != static, это разные ключевые слова с разным поведением, мало того что Вы используете их без понимая того для чего они нужны, так еще пытаетесь с умным видом доказать, что все ок, тем самым возможно вводя в заблуждение людей, которые читают эти комментарии… и с чего Вы вдруг решили, что static нельзя использовать для членов и методов класса???

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

                                      подучите все-таки матчасть, перед тем как вводить окружающих в заблуждение свои праведным напором, а то некрасиво как-то получается :)
                                        0
                                        1 — Да причём здесь защита от повторного включения? Речь ведь об include guards? И как же они помогут если проблема на этапе линковки?

                                        2 — я знаю, что inline != static, и знаю для чего они нужны. Что вы опять кричите не разобравшись? С чего вы решили, что static можно использовать для членов и методов класса? Вы же понимаете, что у ключевика static несколько смыслов, и когда я говорю что его нельзя использовать для членов класса это означает не
                                        class X
                                        {
                                        static void f();
                                        };


                                        а совсем другое:

                                        class X
                                        {
                                        void f();
                                        };

                                        static void X::f() {} // compilation error !
                                        inline void X::f() {} // OK


                                        Я как бы понимаю, что static void f() {} и inline void X::f() {} это не совсем одно и то же, но практически и то и другое может использоваться для внутренней линковки.

                                        Если вы считаете, что inline void f() {} обязательно означает, что код функции будет заинлайнен, то извините, но это вам нужно учить матчасть. На практике, различие между inline и static в этом контексте будет очень маленькое — и в том, и в другом случае функция может быть как заинлайнена, так и нет, как решит компилятор.

                                        3 — экспорт естественно в смысле экспорта из юнита для внешней линковки, а не в смысле экспорта из dll, неужели это не очевидно?

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

                                          «inline void f() {} обязательно означает, что код функции будет заинлайнен» я этого нигде не писал, будет заинлайнен код помеченный как inline решает компилятор, но это не значит что нужно использовать inline для внутренней линковки, когда для этого есть один из вариантов static… хотя я сейчас краем глаза глянул описание inline в стандарте и судя по описанию он действительно может использоваться в таком контексте, но мне непонятно зачем это делать если есть специальное средство…

                                          относительно того, что static не сработает для методов класса… откуда Вы взяли такой пример?

                                          class X
                                          {
                                          void f();
                                          };

                                          static void X::f() {} // compilation error!
                                          inline void X::f() {} // OK

                                          для решения какой проблемы необходимо писать такой код?

                                          относительно экспорта очевидно, но после непоняток с inline я решил удостовериться, не сочтите это за проявление высокомерия с моей стороны…
                                            0
                                            > для решения какой проблемы необходимо писать такой код?

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

                                            Заметьте, в стандарте написано, что реализации функций внутри класса по определению inline. Но этот inline не означает, что код функции будет заинлайнен. То есть фактический смысл — это внутренняя линковка. Со static'ом вообще кранты, у него минимум 3 разных семантики. Ну и для полного счастья ещё добавили безымянные области видимости, которые тоже позволяют сделать внутреннюю линковку. И вы мне хотите сказать, что всё чисто и очевидно? :)

                                            Я был бы очень рад где-нибудь прочитать чёткую терминологию, но у меня такое ощущение что такого места просто нет, и со всеми этими перегрузками ключевиков вроде static все путаются :\ Я смотрел в стандарт, сильно яснее не стало…
                                              0
                                              теперь понятно… как говрится, век живи век учись, а помрешь все равно дураком… :) у меня просто никогда не возникало потребности так делать, так как всягда оставлял по-минимуму в хидере, только интерфейс, вся реализация, даже простые геттеры и сеттеры, в сипипишник, а то сегодня это простой геттер, а завтра не очень простой… да и члены класса так же полезно за pimpl спрятать… ну это уже у каждого свое кунг-фу…

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

                                              за терминологией мне кажется в стандарт или к страуструпу, как первоисточникам… хотя на мой взгляд оба эти манускрипта достаточно сложно перевариваемые, лучше почитать мейерса, саттера и александреску, у них все сжато и по делу, без лишней воды и с терминологией вроде все в порядке…
                                                0
                                                Да уж, что-что, а соскучиться с++ точно не даст…
                                                Действительно, нужно перечитать снова классиков, каждый раз замечаю что на что-то в прошлый раз не обратил внимания, в этот раз это может быть терминология :)
                                                  0
                                                  Вы бы другу другу плюсовали комменты, что ли.
                                                    0
                                                    Зачем?
                                    0
                                    в книжках просто стараются не нагружать читателя лишней информацией в примерах, по это причине могу использоваться примитивные конструкции… так же как например всегда опускается обработка ошибок для простоты примера кода… часто об это указывают в комментариях…

                                    ну а по литературе тут вроде все давно все устаялось и не раз уже перечислялись все значимые книжки, основные наверно следующие:

                                    www.ozon.ru/context/detail/id/2381848/
                                    www.ozon.ru/context/detail/id/1273200/
                                    www.ozon.ru/context/detail/id/2342923/
                                    www.ozon.ru/context/detail/id/1224782/
                                    www.ozon.ru/context/detail/id/2610625/
                                    www.ozon.ru/context/detail/id/2623946/
                                    www.ozon.ru/context/detail/id/3960662/
                                    www.ozon.ru/context/detail/id/85559/
                                    www.ozon.ru/context/detail/id/2576269/
                                    www.ozon.ru/context/detail/id/4751845/
                                      0
                                      Спасибо, конечно, за список книжек, но я в общем-то спрашивал не просто про книжки по плюсам, а вполне конкретный вопрос про фабрики возвращающие не голые указатели… Мне кажется, возвращение обёртки вместо умного указателя не сильно усложнит примеры, но почему-то я такого не помню из книжек.
                                        0
                                        относительно фабрик, тут вообще книжки по плюсам не нужны, достаточно прочитать одну — паттерны проектирования от банды 4х, там кста помимо примеров на плюсах, есть примеры и на смолталк, если вдруг так смущают голые указатели в примерах…

                                        Вам так кажется потаму что Вы знакомы с этой концепцией, а представьте человека, которые еще с ней не знаком, но решил почитать про паттерны, для него это будет лишняя на тот момент информации, которая может затруднить восприятие основной темы…
                0
                @datacompboy:
                habrahabr.ru/blogs/cpp/118368/
                «Стоит сразу оговориться, что такой подход мне нигде не встречался. Так что будем своего рода первооткрывателями.»
                Хочется отписать в комментах: поздравляю, вы изобрели сборщик мусора.
                  0
                  Имелся ввиду подход, где не используется new и delete в явном виде, при этом объекты создаются в куче. Даже, например, в Java этого не видел, хотя там есть сборщик мусора, но везде и всюду разбросаны new .
                  0
                  Конечно, хочется выразить благодарность человеку, отобравшему кусочек нажитого непосильным трудом…

                  Касательно макросов, поясню свою позицию:
                  Макросы — это препроцессор, а не средство самого языка, причем доставшееся еще из С. Думаю это понятно.
                  Не всегда макросы могут помочь сделать код более читабельным и понятным. Приходится помнить, что в данной вот точке кода, определенное слово будет подменено препроцессором на некую штуку, которая объявлена где-то там. Особую радость доставляет разбираться в таком коде, особенно когда макрос включает в себя другой макрос, а то еще один. Очень весело, бывает, отлаживать такой код в дебаггере. Когда видишь в тексте одно, а для дебаггера, в бинарном файле, там нечто другое. Приходится скакать по макросам, инклудам, разбираться, что же там на самом деле происходит.
                  Иногда макросы могут завуалировать собой некоторые проблемы, которые могут вылезти потом в рантайме.
                  Очень плохой практикой считаю задание всяких вычислительных значений посредством макросов, т.к. данные вычисления будут производится Каждый раз, в Каждой строчке кода, где используется этот макрос! В то время, как значения заданные посредством той же константы будут вычислены компилятором на этапе сборки программы и в дальнейшем будет использоваться уже вычисленное значение.
                  Поэтому в свете сказанного я предпочитаю использовать inline-функции, константы, тайпдефы и прочие средства Языка, а не Препроцессора.
                  Разумеется все вышесказанное мое глубокое имхо, основанное на личном опыте.
                  Просто стало интересно, зачем юзать макросы в C++ коде, в то время как космические корабли бороздят просторы большого театра в 2011 году.
                    0
                    спешу Вас обрадовать и заверить, что ваше глубокое имхо это нормальная практика профессионального программирования на с++ :)
                  +2
                  Статья очень интересная, но не слишком ли усложняется singleton?
                  Для подавляющего большинства задач хватит и реализации RSDN (последний шаблонный вариант).
                    +1
                    Я, конечно, извиняюсь, но по-моему, так писать вообще не стоит. Зачем мне помнить, что где-то там надо освободить синглтон? К тому же такой подход содержит абсолютно все недостатки, которые указаны в первой статье.
                    –2
                    template<typename T>
                    T& single()
                    {
                        static T t;
                        return t;
                    }


                    Если мне не изменяет память, так делать нельзя — стандарт C++ не гарантирует, что static объект в функции переживет вызов функции. То есть конкретные реализации C++ так делают, но ничто не защищает нас от повторения в будующем истории с memcpy, если какая-то реализация будет разрушать объект при выходе из функции и восстанавливать его при повторном вызове. По стандарту — имеют полное право так делать. ИМХО, закладываться на не описанную в стандарте реализацию компиляторов — не лучшая идея.

                    Или память мне все-таки изменяет?
                      +4
                      > Если мне не изменяет память, так делать нельзя — стандарт C++ не гарантирует, что static объект в функции переживет вызов функции.

                      Изменяет, потому что гарантирует. Этот приём иногда называют синглтоном Майерса. У этого приёма есть преимущества и недостатки.
                        0
                        То есть 3.6.3(1), «If a function contains a local object of static storage duration that has been destroyed and the function is called during the destruction of an object with static storage duration, the program has undefined behavior if the flow of control passes through the definition of the previously destroyed local object.» вас не настораживает?

                        Скажем так — я бы постерегся трактовать 3.7.1(1) и 12.4(10) литерально при наличии 3.6.3(1) и 3.7.1(3) и, особенно, 3.7.1(2).

                        Фишка, ИМХО, заключается в том, что стандарт разделяет определения «static storage duration» и «static local variable». Причем второе везде идет как исключение (3.6.3, 3.7.1).

                        Хотя, в целом, соглашусь с вами — память меня подвела, ничего фатально противоречащего стандарту в таком использовании нету.

                        Можно переходить к многопоточности? :)
                          0
                          Переходить к многопоточности еще рано. Про это будет через одну статью.
                          • UFO just landed and posted this here
                              0
                              Это только часть того, что бы я хотел описать.
                            +1
                            Неверно толкуете стандарт. Этот пункт говорит о том, что поведение не определено в следующем случае:

                            struct A {};
                            
                            void foo() { static A a; };
                            
                            struct B {
                              ~B() { foo(); };
                            };
                            
                            void main() {
                            
                              static B b;
                              foo();
                            
                            };
                            
                        +2
                        Александреску отлично описал 3 разных способа создания синглтона для разных случаев, просто и понятно, к чему у вас такие сложности?
                          0
                          Этот же комментарий был к первой статье. Приведу его еще раз:

                          «Речь в ней идет не о реализации, а об использовании… Можно взять реализацию из Александреску. Книжка его очень умная и толковая. Но вся статья написана о том, как использовать и убрать недостатки, присущие этому паттерну.»
                            0
                            Вы знаете, мне кажется, вы просто не умеете готовить Loki:

                            #include "stdafx.h"
                             
                            #include <iostream>
                            #include <Singleton.h>
                             
                            template< class T >
                            struct An
                            {
                                typedef Loki::SingletonHolder< T > TSingleton;
                             
                                An(){ TSingleton::Instance(); }
                             
                                T &operator*(){ return TSingleton::Instance(); }
                                T *operator->(){ return &TSingleton::Instance(); }
                            };
                             
                            struct A
                            {
                                A(){ std::cout << "A" << std::endl; a = 1; }
                                ~A(){ std::cout << "~A" << std::endl; a = -1; }
                             
                                int a;
                            };
                             
                            struct B
                            {
                                An< A > pa;
                             
                                B(){ std::cout << "B" << std::endl; }
                                ~B(){ std::cout << "~B" << std::endl; out(); }
                             
                                void out(){ std::cout << pa-><< std::endl; }
                            };
                             
                            int _tmain( int, _TCHAR*[] )
                            {
                                An< B > pb;
                                pb->out();
                             
                                return 0;
                            }
                             
                              0
                              А ведь там еще есть явный контроль времени жизни, поддержка многопоточности, феникс синглтоны, проверки для исключения использования разрушенных объектов и т.д.
                                0
                                Вы можете использовать вместо синглтона Майерса синглтон Александреску без каких-либо проблем. Я использовал наиболее простой способ для демонстрации идеи. Про многопоточность будет в другой статье.
                                0
                                Во-первых, предложенный класс An не может иметь синглтоном наследника, поэтому, как частный случай, T не может быть абстрактным. Во-вторых, An может использовать любую реализацию, которая заливается непосредственно в класс, а не обязательно синглтон (см. первую статью). В-третьих, нет отложенности вычислений, что вызовет проблемы с зависимостями.
                                  0
                                  я вас понял, но все-таки попробуйте вынести ваши create и т.п. в стратегии, как-то так:

                                  #include "stdafx.h"
                                   
                                  #include <iostream>
                                  #include <memory>
                                   
                                  // Loki::CreateUsingNew with abstract classes support.
                                  template< class T, class I = T >
                                  struct CreateUsingNew
                                  {
                                      static I* Create(){ return new T; }        
                                      static void Destroy( I* p ){ delete static_cast< T * >( p ); }
                                  };
                                   
                                  // Use CreateUsingNew< T > as the default creation strategy.
                                  template< class T >
                                  struct SelectCreationStrategy
                                      : CreateUsingNew< T >
                                  {
                                  };
                                   
                                  template< class T, template< class > class TCreationStrategy = SelectCreationStrategy >
                                  class An
                                  {
                                      std::shared_ptr< T > _guard;
                                   
                                      T &get()
                                      {
                                          static std::shared_ptr< T > p( TCreationStrategy< T >::Create()&TCreationStrategy< T >::Destroy );
                                   
                                          _guard = p;
                                   
                                          return *p;
                                      }
                                   
                                  public:
                                   
                                      T &operator*(){ return get(); }
                                      T *operator->(){ return &get(); }
                                  };
                                   
                                  // Note: usage of this singleton is the same as in the previous variate, no macro, etc.
                                  struct A
                                  {
                                      A(){ std::cout << "A" << std::endl; a = 1; }
                                      ~A(){ std::cout << "~A" << std::endl; a = -1; }
                                   
                                      int a;
                                  };
                                   
                                  // Abstract
                                  struct B 
                                  {
                                      B(){ std::cout << "B" << std::endl; }
                                      ~B(){ std::cout << "~B" << std::endl; }
                                   
                                      virtual void out() = 0;
                                  };
                                   
                                  // Impl
                                  struct C : B
                                  {
                                      An< A > pa;
                                   
                                      C(){ std::cout << "C" << std::endl; }
                                      ~C(){ std::cout << "~C" << std::endl; out(); }
                                   
                                      virtual void out(){ std::cout << pa-><< std::endl; }
                                  };
                                   
                                  // Overridden creation strategy for B.
                                  // Note: you can use even custom allocators here.
                                  template<>
                                  struct SelectCreationStrategy< B >
                                      : CreateUsingNew< C, B >
                                  {
                                  };
                                   
                                  int _tmain( int, _TCHAR*[] )
                                  {
                                      An< B > pb;
                                      pb->out();
                                   
                                      return 0;
                                  }
                                   


                                  Имхо читабельнее, плюс есть возможность не-new выделения памяти, плюс поддержка семантики глубокого копирования на уровне стратегий (нужно еще Clone добавить в стратегию естественно)… хотя я очень слабо понимаю, зачем синглетонам может понадобиться глубокое копирование)
                                    0
                                    Можно ли залить любую реализацию в класс An руками, например класс D, который наследует B (пункт «во-вторых»)?
                                      0
                                      template<>
                                      struct SelectCreationStrategy< D >
                                           : CreateUsingNew< D, B >
                                      {
                                      };

                                      Если нужно, допустим, какой-нибудь параметр еще передать, можно так:
                                      template<>
                                      struct SelectCreationStrategy< D >
                                      {
                                           static B* Create(){ return new D( /* something */ ); }        
                                           static void Destroy( B* p ){ delete static_cast< D * >( p ); }
                                      };

                                      Естественно, все это должно быть в h файле
                                        0
                                        Я хотел следующее: по умолчанию заливается C неявно, но иногда я хочу явно заливать D. Такое возможно? Далее, хотелось бы, чтобы решение о том, что заливать неявно, находилось в cpp файле.
                                          0
                                          что-то я не понимаю… это же синглетон, как понять «иногда»? Для всех тех ситуаций, когда вместо C должно быть D должна быть определена соответствующая специализация SelectCreationStrategy до того как будет попытка использовать синглетон соответствующего типа («до» имеется в виду по коду, а не по времени).

                                          ну в вашем коде ведь так же…

                                          можно еще писать An< C, TCreationStrategy_1 > и An< C, TCreationStrategy_2 > с явным указанием стратегии, только это уже будет 2 разных синглетона с одинаковым интерфейсом.

                                          В общем, можно подумать, только опишите реальную ситуацию
                                            0
                                            Написал Update в конце статьи. Если есть еще вопросы, спрашивайте. Тема оказалась непростой, хотя мне поначалу казалось, что все просто.
                            +1
                            К чему такие сложности? 95% юзкейсов описывается первым шаблоном. А если не описываются, то надо еще архитектуру пересмотреть. А то бывает, что один синглтон от другого зависит и начинаются чудеса.
                              0
                              Отложенность вычислений для этого синглтона позволяет правильно разрулить зависимости при инициализации. Контроль времени жизни разруливает уничтожение.
                                +1
                                Зависимости при инициализации надо стараться избегать а не разруливать, тогда волосы целее на голове будут.
                                  0
                                  Все зависит от сложности задачи. Бывают настолько сложные системы, что просто так разрулить не получается. Предложенный подход эту проблему решает на корню. Это как с shared_ptr: можно его и не использовать и самому следить за памятью. Но зачем?
                              +1
                              И вообще более того, лучше даже сделать namespace с нужными методами, а все общие поля хранить в чем-нибудь типа

                              static scoped_ptr ptr;

                              Тогда никакая зараза не сможет удалить синглтон и можно вполне нормально рулить его временем жизни. Но вообще делать что-то в деструкторе синглтона — это ходить по граблям.
                                0
                                scoped_ptr нельзя копировать, поэтому нельзя контролировать время жизни подобно тому, как описано в статье. По поводу грабель вы правильно написали, но предложенный подход эти грабли убирает.
                                  0
                                  Так можно любой pointer сделать внутри реализации, зато интерфейс будет чистым как слеза младенца.
                                    0
                                    главное не забудьте про циклические ссылки
                                  0
                                  Будем использовать умный указатель из стандартной библиотеки std::shared_ptr заголовочного файла memory. Стоит отметить, что такой класс доступен для современных компиляторов, которые поддерживают стандарт C++0x. Для тех, кто использует старый компилятор, можно использовать boost::shared_ptr.


                                  Да вроде не обязательно использовать новейшие компиляторы или буст:
                                  sveolon@sveolon-laptop ~/build/tmp $ cat ./main.cpp
                                  #include <tr1/memory>
                                  #include int main()
                                  {
                                  std::tr1::shared_ptr p (new int(3));
                                  std::cout << *p << std::endl;
                                  return 0;
                                  }
                                  sveolon@sveolon-laptop ~/build/tmp $ g++ ./main.cpp
                                  sveolon@sveolon-laptop ~/build/tmp $ ./a.out
                                  3
                                  sveolon@sveolon-laptop ~/build/tmp $ g++ --version
                                  g++ (Ubuntu 4.4.3-4ubuntu5) 4.4.3
                                  Copyright © 2009 Free Software Foundation, Inc.
                                  This is free software; see the source for copying conditions. There is NO
                                  warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

                                  sveolon@sveolon-laptop ~/build/tmp $

                                  Only users with full accounts can post comments. Log in, please.