1. C++ / Говнокод #14581

    +58

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    17. 17
    18. 18
    19. 19
    20. 20
    21. 21
    22. 22
    23. 23
    24. 24
    25. 25
    26. 26
    27. 27
    28. 28
    29. 29
    30. 30
    31. 31
    32. 32
    33. 33
    34. 34
    35. 35
    36. 36
    37. 37
    38. 38
    39. 39
    40. 40
    41. 41
    42. 42
    43. 43
    44. 44
    45. 45
    46. 46
    template <typename T>
    class MySharedPtr{
    public:
      explicit MySharedPtr(T* obj) : _obj(obj){}
      // --> я дописал
      MySharedPtr(const MySharedPtr& other) : _obj(other._obj){ inc_ref_count(_obj);}
      // <-- я дописал
      MySharedPtr& operator=(const MySharedPtr& other) {
        // --> я дописал
        if (this == &other)
          return *this;
        // <-- я дописал
        _obj = other._obj;
        inc_ref_count(_obj);
      }
      ~MySharedPtr(){
        dec_ref_count(_obj);
      }
    
    private:
      static void inc_ref_count(T* obj){
        std::lock_guard<std::mutex> lock(_mutex);
        _ref_count[obj] ++ ;
      }
    
      static void dec_ref_count(T* obj){
        std::lock_guard<std::mutex> lock(_mutex);
        if (--_ref_count[obj]){
          delete obj;
          _ref_count.erase(_ref_count.find(obj));
        }
      }
    
      T* _obj;
    
      static std::mutex MySharedPtr<T>::_mutex;
      static std::map<T*,int> MySharedPtr<T>::_ref_count;
    };
    
    
    template <typename T>
    std::map<T*,int> MySharedPtr<T>::_ref_count;
    
    
    template <typename T>
    std::mutex MySharedPtr<T>::_mutex;

    сегодня приходил чел-выпускник, написал на листочке shared_ptr, какое ваше мнение?

    Запостил: LispGovno, 14 Февраля 2014

    Комментарии (63) RSS

    • Нелепо.
      Ответить
    • > template <typename T> std::mutex MySharedPtr<T>::_mutex;
      Эту ашку можно инклудить только один раз на проект? Или я отстал от жизни, и в каком-нибудь с++11 это стало работать?

      Защищенный единым мутексом глобальный мап со счетчиками ссылок вообще порадовал ;) Прощайте, остатки перфоманса, нам будет вас не хватать.

      В операторе присваивания вы с испытуемым забыли декрементнуть счетчик старого указателя.

      P.S. А человека берите на испытательный, мозги есть, кресты на базовом уровне знает. Остальное придет с опытом.
      Ответить
      • Тут, имхо, главное не то, сумел или не сумел он написать шаропоинтер. На листочке да еще за ограниченное время даже такой мэтр как defecate++ спокойно может зафейлить в мелочах...

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

        А знание деталей STL (например у map'а есть erase с ключем, и не надо там делать find) и типовых решений (хранение счетчика в дополнительном объекте) - это дело наживное. Если атмосфера в коллективе у вас нормальная, и его одного не будете бросать на произвол судьбы - прокачается.
        Ответить
      • > Защищенный единым мутексом глобальный мап со счетчиками ссылок вообще порадовал ;) Прощайте, остатки перфоманса, нам будет вас не хватать.

        Видимо ему сказали, что пиздец как важно, чтобы указатель указывал именно на T, а не на T_with_additional_info
        Ограниение нелепое, нужно лишь чтобы посмотреть как будет выкручиваться.
        Ответить
        • Ну тому же boost::intrusive_ptr дополнительная память под управляющий блок не нужна. Зато нужна поддержка со стороны самого объекта.
          Ответить
          • ИМХО суть одинаковая : кроме чистых данных нужна ещё какая-то доп хрень для управления
            Ответить
            • Ну да. По крайней мере счетчик куда-то надо засунуть - или к данным, или в отдельный блок, выделяемый из пула.

              Есть, правда, извращенный способ - смартпоинтеры объединить в кольцевой список. Но один такой смартпоинтер будет весить как 3 указателя, да и потокобезопасно хер запилишь эту конструкцию.
              Ответить
              • Меня больше всего бесит, что придётся в конструкторе копирования старый объект ковырять, по-моему это плохо.
                А кольцевой список по любому неизбежен для слабых указателей же
                Ответить
                • показать все, что скрытоЯ осквернял мать "bormand`a".
                  Ответить
                • > А кольцевой список по любому неизбежен для слабых указателей же
                  Не. Они, если я правильно помню, вообще тупо сделаны. В управляющем блоке 2 счетчика - сильных и слабых. Когда сильный счетчик упал до нуля - объект удаляется, и указатель в управляющем блоке зануляется. Когда слабый счетчик упал до нуля - удаляется управляющий блок. А слабый счетчик уменьшается когда слабую ссылку переадресовывают на что-то другое, или когда в управляющем блоке уже null и ее пытаются сконвертить в сильную. Как-то так.
                  Ответить
                  • То есть слабые указатели внутри делают двойное разыменование?
                    Ответить
                    • Слабые указатели вообще юзать нельзя. Их можно только копировать и конвертить в сильные.

                      А сильные - ну как запилишь. Если хочешь размером с указатель - будет двойное разыменование. Если размером с джва указателя - хватит одного.
                      Ответить
                      • > Слабые указатели вообще юзать нельзя. Их можно только конвертить в сильные.

                        В смысле, слабые нельзя разыменовывать?
                        Ответить
                        • > В смысле, слабые нельзя разыменовывать?
                          Именно так. Ибо нехуй. Она же не удерживает объект от смерти, и он посреди твоего алгоритма может принять ислам.
                          Ответить
                          • Я вообще тогда не понял, нахрен тогда они нужны.
                            Я думал, это такие указатели, которые как сырые, только ещё и умеют предупреждать, если объект сдох, а ты пытаешься обратиться.
                            Ответить
                            • > умеют предупреждать, если объект сдох, а ты пытаешься обратиться
                              А они умеют:
                              // давным давно в одной далекой галактике...
                              std::shared_ptr<Cat> strong_cat(new Cat());
                              std::weak_ptr<Cat> weak_cat = strong_cat;
                              
                              void test() {
                                  // мы не знаем, мертв или жив кот, на которого ссылается weak_cat
                                  // поэтому откроем ящик и зафиксируем это состояние
                                  std::shared_ptr<Cat> cat(weak_cat.lock());
                                  // этот указатель спасет кота до конца блока, если он еще жив
                                  // поэтому мы можем гладить кота не боясь сегфолта
                                  if (cat) {
                                      // кот жив. погладь кота, сука
                                  } else {
                                      // кот мертв :(
                                  }
                              } // здесь shared_ptr теряет свою силу, и кот может умереть, если он еще жив
                              > нахрен тогда они нужны
                              1) Для разрыва циклических зависимостей - например от владельцев к рабам юзаешь сильный указатель, а от рабов к владельцам - слабую.
                              2) Для всяких кешей. Но тут стоит позаботиться о сборке мусора, периодически пробегаясь по указателям, и вычищая дохлые.
                              Ответить
                              • > а от рабов к владельцам - слабую.

                                И что, раб не может позвать владельца?
                                Почему бы тогда вообще не убрать эту ссылку, если её даже разыменовывать нельзя и на время жизни она не влияет? Какой-то Ъ-инкапсулятивный объект получается, не хуже, чем в HQ9+
                                Ответить
                                • > И что, раб не может позвать владельца?
                                  Может. Временно преобразовав указатель в сильный. См. пример с котом.

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

                                  Если ты оставишь одну жесткую ссылку от владельца к рабу - раб не сможет вызывать владельца.

                                  Если ты вместо weak_ptr от раба к владельцу поюзаешь просто указатель или простую ссылку - владелец может принять ислам в то время, пока раб с ним общался.
                                  Ответить
                                  • > Если ты вместо weak_ptr от раба к владельцу поюзаешь просто указатель или простую ссылку - владелец может принять ислам в то время, пока раб с ним общался.


                                    Аааааа, я понял! Тредоблядские проблемы!
                                    Ответить
                                    • > Тредоблядские проблемы!
                                      Они самые ;)
                                      Ответить
                                      • >> Тредоблядские проблемы!
                                        > Они самые ;)
                                        А можно разве вик_птр локать в шаред, в то время как последний шаред_птр уходит на покой? Не будет рейс кондишена?
                                        Ответить
                                        • показать все, что скрытоКто минуснул - тот Филипп Киркоров.
                                          Ответить
                                        • > А можно разве вик_птр локать в шаред, в то время как последний шаред_птр уходит на покой? Не будет рейс кондишена?
                                          Можно. Рейс кондишена не будет. Либо ты залочишь его, и шаред_птр не успеет уйти, либо он успеет уйти, а тебе достанется нулл.

                                          Нахер они вообще нужны без этой фичи? :)
                                          Ответить
                              • > кот жив
                                > else
                                > кот мертв :(
                                Наблюдатель виноват.
                                Ответить
                            • > Я вообще тогда не понял, нахрен тогда они нужны.
                              Задаюсь аналогичным вопросом c того момента, как узнал про их отношения с shared_ptr. Знаю только, что weak_ptr используется внутри enable_shared_from_this, но явно он мне не пригодился ни разу.
                              Ответить
                              • > Знаю только, что weak_ptr используется внутри enable_shared_from_this
                                А я юзал shared_from_this на практике :) Нужно было из метода одного объекта запиливать другие, передавая им шаропоинтер на себя. Но this передать было нельзя, т.к. он уже управлялся через shared_ptr, а отдать один указатель на растерзание двум шаропоинтерам - жди беды.

                                Вот в таких случаях и нужен shared_from_this().
                                Ответить
                                • > Вот в таких случаях и нужен shared_from_this().
                                  Ага, я тоже стал пользоваться; в некоторых классах конструкторы попрятались в фабрики, производящие shared_ptr<T>. Я просто не могу представить ситуацию, при которой нужно явно использовать weak_ptr. Пример, в котором два объекта содержат ссылки друг на друга и не могут от этого самоудалиться, выглядит несколько... м-м-м... надуманно :)
                                  Ответить
                                  • да ну не настолько надуманно, как тебе кажется

                                    допустим, ты пишешь tcp-сервер
                                    у тебя есть объект connection, он создается и живёт строго как shared_ptr<connection> (кстати, тут как раз везде и пригождается ему shared_from_this()), хранит в себе socket и делает всё что ему полагается как соединению - ждёт прихода данных от клиента, обрабатывает их, отвечает клиенту, ждёт следующей порции данных (ну типа как persistent).

                                    и у тебя есть объект server, который всегда держит наизготовке свежий shared_ptr<connection>, чтобы акцептить на его объекте socket

                                    ну так вот, коннекшен в любой момент может принять решение закончиться - ну, например, сокет уже закрылся, ну или логическая ошибка - и это его право

                                    однако, объект server тоже могут заставить принудительно умереть - например, приложение/сервис штатно останавливают.

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

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

                                    вот тут-то двум объектам и приходится держать друг на друга ссылки

                                    p.s. собственно задача элегантно и элементарно решается как раз тем, что коннекшен держит на сервер лишь weak_ptr, тогда в итоге все умирают в конце, приятного просмотра
                                    Ответить
                                    • > http://govnokod.ru/14581#comment217212

                                      Как раз сегодня именно с этим и разбирался :) Предположил, что мне понадобится weak_ptr, но обошелся без него. Вот сумбурное описание моего решения:

                                      Есть connection, который содержит в себе сокет и создается только в shared_ptr.

                                      Есть connection_manager, который хранит в себе живые подключения: vector<shared_ptr<connection> >

                                      Также connection_manager содержит acceptor и метод begin_accept(), который выглядит примерно так:
                                      void connection_manager::begin_accept()
                                      {
                                      	connection::ptr_type conn = connection::make();
                                      	acceptor_.async_accept(conn->get_socket_ref(),
                                      		boost::bind(&connection_manager::accept_handler, this, _1, conn));
                                      }
                                      // accept_handler объявлен следующим образом: 
                                      void connection_manager::accept_handler(
                                      		const boost::system::error_code & error,
                                      		connection::ptr_type accepted_connection);
                                      Благодаря boost::bind, объект conn, в который нужно принять клиента, "доживает" до вызова accept_handler().

                                      Если клиент принят без проблем, то он добавляется в список живых клиентов и продолжает жить вместе с ним.

                                      Если клиента принять не удалось - объект самоуничтожится по выходу из begin_accept().

                                      connection содержит коллбек, который вызывается при разрыве соединения (зафейленный read_handler или write_handler, закрытие соединения со стороны клиента). Этот коллбек настраивается на удаление клиента из списка connection_manager.

                                      Сервер сделан несколько примитивно и вряд ли масштабируется хотя бы до 1000 подключений. Тестировал поверхностно, логи пишут, что все ресурсы освобождаются корректно.
                                      Ответить
                                      • > connection содержит коллбек
                                        воот
                                        очевидно, кококоллбек на объект connection_manager
                                        ну типа boost::bind(&connection_manager::on_dead_connection, this, _1, ...), верно? (сужу по твоему обращению с accept_handler)

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

                                        и в итоге вместо приятно умершего приложения, пользователь видит противное "БАМЦ, приложение обратилось к необратимому и допустило недопустимое!"

                                        как решаем
                                        ага! connection_manager надо тоже пошарить, и в коллбек коннекшену скормить boost::bind(&..., shared_from_this(), ...)
                                        вуаля - получили два взаимосвязанных по shared_ptr объекта
                                        Ответить
                                        • > очевидно, кококоллбек на объект connection_manager
                                          > connection_manager драматично умирает в одном потоке
                                          > всё ещё живой connection <...> пытается вызвать у этого connection_manager

                                          Коллбеку on_dead_connection биндится самоудаление из сервера в случае успешного акцепта.

                                          А перед смертью акцептор закрывает себя, поэтому handle_accept не должен завершиться успешно после смерти сервера. Напоминает говнокод if(this != NULL) { /*...*/ }

                                          > всё ещё живой connection <...> пытается вызвать у этого connection_manager злосчастный коллбек - по мёртвому объекту

                                          Можно обойти - сделать обработчик handle_accept статической функцией и привязывать к нему shared_ptr<connection_manager> и shared_ptr<connection>.

                                          Перед вызовом async_accept они друг с другом никак не связаны.
                                          Успешный accept - и живой connection становится рабом живого connection_manager.
                                          Фейл - connection умирает и они друг на друга не ссылаются.
                                          Ответить
                                          • что ж тебя всё на accept зациклило
                                            проблема не в нем, он у тебя более-менее правильно написан, там вообще сложно напортачить

                                            проблема будет дальше, когда всё уже давно заакцепчено, работает и начинает помирать, перечитай ещё раз, что я написал
                                            Ответить
                                            • ещё раз, в двух словах

                                              у тебя в N потоках работают коннекшены
                                              работают асинхронно
                                              о них знает connection_manager

                                              connection_manager собрался помереть
                                              командует своим коннекшенам из списка "умрите"
                                              и как бы хочет теперь и сам помереть

                                              теперь у тебя дилемма:
                                              1) либо теперь запрещено всем-всем коннекшенам вызывать коллбек, и придется городить адовый ад с учетом расовых кондиционеров
                                              2) connection_manager запрещено умереть, пока не помрут все его connection - опять же адовый ад, например, с семафором (т.е. mutex+condition_variable+counter), а поток, в котором вызвали деструктор connection_manager, должен терпеть

                                              просто я всё это уже в старых проектах проходил
                                              я ваши грабли замечаю ещё до того, как вы о них мне сказали
                                              Ответить
                                            • О, Великий Разум Defecate++, прими мою благодарность в виде плюсика, да не истопчутся твои пальцы о клавиатуру!
                                              Дошло :) Будем думать.
                                              Ответить
                                              • да пожалуйста

                                                иногда мне кажется, что впору сесть и писать туториал "пишем свой сервер на асио на коленях"
                                                Ответить
    • показать все, что скрытоЯ заметил, что вы все так нежно сосёте, что пиздец.
      Ответить
    • Не, ну а что, за 15 минут в условиях стресса сгенерировать на листике внятный поделенный_укзтл мало кто сможет.
      Эти все атомарные счетчики циклов для меня, например, до сих пор темный лес. Порой кажется, что авторы статей на тему копипастят друг у друга одно и то же.
      А карта счетчиков ссылок имеет забавный побочный эффект: если в конце программы она не пустая, то поделенный_укзтл косячный :)
      Ответить
      • Да нормальный шаропоинтер даже без стресса и с компом хрен запилишь с первого раза... Особенно потокобезопасный. Особенно с поддержкой слабых указателей.
        Ответить
    • показать все, что скрыто
      Ответить
    • А мне похуй на ваши проблемы, у меня есть только unique_ptr и raw_ptr.
      Ответить
      • да тебе и на многопоточность похуй
        что теперь то
        не у всех целерон, кому-то иногда хочется выжать из железок больше, чем это было возможно в 2001 году
        Ответить
        • И на исключения.

          Вообще я тут сижу и с умным видом, как сеньёр кокойта, обсуждаю код соискателя, а на деле оказалось, что мой реальный код - нубский отстой, дающий по 100 предупреждений на всех компиляторах, кроме моего, да ещё и багующий в релизе из-за странных оптимизаций. Чувствую себя дерьмом.
          Ответить
        • Не, ну в самом деле, был у меня огромный такой ебанутый граф, в котором ссылки создавались и уничтожались и ввершины новые то создавались то забывались и ваще связи летали туда сюда, только один нюанс - количество операций по реорганизации графа у меня было заведомо конечно. Так мне похуй - завёл отдельное хранилище ссылок на все вершины, а связи между вершинами сделал сырыми. Ну и новые вершины добавлял только через хранилище.

          А потом хранилище грохал в самом конце. Потом, правда оказалось что программа много жрёт памяти, и я немного оптимизировал (хотя мог и не делать), после загрузки каждого графа делал обход вершин и убирал недостижимые.

          Да, я эмулировал сраную гэцэшечку)))
          Но никакие счётчики ссылок и слабые указатели мне были нахуй не нужны.
          Ответить
          • ты будешь визжать как девка, когда сборщик мусора добавят в очередной стандарт с++!
            Ответить
            • показать все, что скрытоГосподи, как толсто. Я в шоке.
              Ответить
              • За что минус? Сишка - это горн, молот и наковальня. Никаких готовых решений, кроме самого основного. Нужны инструменты для чего-то - куй.
                О каком сборщике мусора может идти речь?
                Ответить
                • В стандарте С++11 уже есть пункт "базовая поддержка сборки мусора". Правда насколько я знаю gcc и clang на этот пункт забили и соответственно стандарту до конца не соответствуют, да и в пункте самого gc не требуется, только функции его "поддержки". Но кто знает что они сделают в следующих стандартах.
                  Ответить
                • > Сишка
                  Но кресты != сишка.

                  А про сишку ты все правильно сказал.
                  Ответить
                  • В С тоже тоже уже есть встроенные комплексные числа, массивы на стеке переменной длины и какой-то restrict.
                    Ответить
            • Нет, господи, НЕЕЕЕТ!!!!
              Ответить
        • > да тебе и на многопоточность похуй
          Может он, узнав о печальной кончине Крея, боится? Ну вот, признает человек, что за параллельными копмутерами будущее, а тут случайно запорожец изза угла - и нет больше с нами ТарасаБ.
          Ответить
          • > The Cray-1 was a supercomputer designed, manufactured and marketed by Cray Research
            Вы про владельца Крей Инкопрорейтед?
            Ответить
            • Чет я сомневаюсь, что он был владелцем, скорее все-таки руководителем.
              И да, там жеж какая печальная история случилась: жил человек, делал самые быстрые в мире суперкомпутеры, и не верил в распределенные системы. В один прекрасный день компутер с распределенной системой его компутер обогнал. Задумался человек, и решил тоже сделать параллельный компутер, опять же, самый быстрый, и тут внезапно его машина сбила...
              Ответить
      • показать все, что скрытоА мне похуй на тебя.
        Ответить
      • > raw_ptr
        stupid_ptr обернули в обертку для однородности?
        Ответить
        • Да, и вынесли в namespace i_know_what_i_do_and_let_the_dead_ostric h_fuck_me_if_my_program_will_crash
          Ответить

    Добавить комментарий