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

    0

    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
    template<typename T>
    class SharedPtr {
        T* value;
        int* ref_count;
    
    public:
        SharedPtr(T* value) : value(value) {
            ref_count = new int;
            *ref_count = 1;
        }
    
        SharedPtr(const SharedPtr& other) {
            value = other.value;
            ref_count = other.ref_count;
            (*ref_count)++;
        }
    
        SharedPtr(SharedPtr&& other) {
            value = other.value;
            ref_count = other.ref_count;
            other.ref_count = nullptr;
        }
    
        ~SharedPtr() {
            if (ref_count == nullptr) {
                return;
            }
    
            if (*ref_count == 1) {
                delete value;
                delete ref_count;
            } else {
                (*ref_count)--;
            }
        }
    
        T& operator *() const {
            return *value;
        }
    
        T* operator ->() const {
            return value;
        }
    };

    Реалейзовал минимальную версию shared_ptr. Есть ошибки/замечания?

    https://ideone.com/g7gqBM

    Запостил: 3_dar, 04 Мая 2021

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

    • Непотокобезопасно. Не поддерживает weak_ptr, нет возможности создать пустой указатель, освободить текущий указатель или присвоить ему другое значение. Если деструктор value бросает исключение — течёт.

      Конструкторы не помечены, как explicit поэтому можно СЛУЧАЙНО отдать владение указателем функции принисающей SharedPtr
      Ответить
      • А бросание исключения в деструкторе - это легально? Я думал кровь кишки распидорасило. Ладно, шутка, я об этом вообще не подумал.
        Ответить
        • Легально, но нерекомендуемо. Хотя в иных случаях либо пики то исключение, либо errno.
          Ответить
      • Я перепесала

        template<typename T>
        class SharedPtr {
            struct Ref {
                T* value;
                int count;
            };
            Ref* ref;
        
        public:
            explicit SharedPtr(T* value) {
                ref = new Ref;
                ref->value = value;
                ref->count = 1;
            }
        
            SharedPtr(const SharedPtr& other) {
                ref = other.ref;
                ref->count++;
            }
        
            SharedPtr(SharedPtr&& other) {
                ref = other.ref;
                other.ref = nullptr;
            }
        
            ~SharedPtr() {
                if (ref == nullptr) {
                    return;
                }
        
                if (ref->count == 1) {
                    auto value = ref->value;
                    delete ref;
                    delete value;
                } else {
                    ref->count--;
                }
            }
        
            T& operator *() const {
                return *ref->value;
            }
        
            T* operator ->() const {
                return ref->value;
            }
        };
        Ответить
        • Починена только утечка при исключении. Всё остальное осталось. Даже краш при копировании перемещённого указателя.

          Главная проблема — это бесполезность данного класса. Его нельзя создать, чтобы заполнить потом. Нельзя заставить указывать на другой объект. Нельзя освободить указатель. Не помещается в контейнеры, использовать его как член класса из-за предыдущих проблем также затруднительно.
          Ответить
          • Я же написал, что это демо версия. Потокобезопасность починена?
            Ответить
            • Нет. Есть 2 указателя в 2х потоках. Один начинает уничтожаться, проверяет, что ref->count == 1, прыгает на else и отдаёт управление другому потоку. Второй поток уничтожает свой указатель, проверяет, что ref->count == 1, прыгает на else, уменьшает ref->count и возвращает управление потоку 1. Тот продолжает исполнять дальше: уменьшает ref->count и успокаивается.

              Итого: оба указателя унчтожены, а ref утёк.
              Ответить
              • Пизлец! И что же делать в такой ситуации?
                Ответить
                • Переходить на "PHP".
                  Ответить
                • Это всё не учитывая, что одновременная модификация count приводит к гонке и может испортить значение.

                  Пишешь: "Этот класс не предназначен для одновременного доступа к объекту из нескольких потоков."

                  Если серьёзно, для начала count делаешь атомиком и переписываешь все доступы к нему на атомарные операции. В деструкторе сначала атомарно уменьшаешь count, затем смотришь, какое значение там реально было при декременте (проверяешь значение fetch_sub). На основании этого уже решаешь, удалять, или нет.
                  Ответить
                  • А что насчёт мютексов, лочек и семафоров?
                    Ответить
                    • Если ты хочешь, чтобы пирфоманс был подобен ПХП, то можно просто всё закрывать мютексом при любой операции. А так желательно, чтобы указатель был бы lock-free.

                      Защищать доступ к содержащемуся объекту — не твоя задача, об этом голова должна болеть у пользователя. Хотя можно сделать какой-нибудь shared_protected_resource, где ты будешь явно забирать ресурс.
                      Ответить
                      • >просто всё закрывать мютексом при любой операции. А
                        Привет, Java Vector
                        привет, Java Hashtable
                        Ну и привет GILы, само собой
                        Ответить
                    • Ёбнулся? Это же тормозное говно будет.
                      Ответить
                      • Не такое уж и тормозное, если у тебя одно ядро
                        Ответить
          • > Починена только утечка при исключении
            Ня самом деле нет, ня починена. Вернее, починена, но слишком радикальня:
            struct X {
                ~X() noexcept(false)
                {
                    throw std::runtime_error("Hello!");
                }
            };
            
            int main()
            {
                try {
                    SharedPtr<X> ptr{ new X() };
                    std::cout << "Created X" << std::endl;
                } catch (const std::runtime_error & e) {
                    std::cout << "Catched " << e.what() << std::endl;
                }
                std::cout << "Goodbye!" << std::endl;
            }

            https://wandbox.org/permlink/kPxhV0OcwNLbEvoA
            > libc++abi: terminating with uncaught exception of type std::runtime_error: Hello!

            Проблема в том, что все деструкторы — по-умолчанию noexcept, а вылетание исключения из noexcept деструктора приводит к мгновенной смерти программы (с очисткой всех ресурсов, ура (─‿‿─)!). Чтобы такого ня было — нужня добавить деструктору SharedPtr спецификатор "noexcept(noexcept(delete std::declval<T*>()))": https://wandbox.org/permlink/FRbyM2DP3XSWYcos .
            Ответить
            • > "noexcept(noexcept(delete std::declval<T*>()))"


              Пошёл нахуй с моей ветки, гандон.
              Ответить
          • ишел 21 век. люди на С++ до сих пор пишут управление указателями.... аххахахах... это как эволюция unix.... шел 21 век но Xorg с экранов не исчезал :)
            Ответить
    • массив нпдржвает?
      Ответить
    • В move-конструкторе не очищается чужое value. Это некритично (double delete не будет), но может приводить к трудноуловимым ошибкам.
      Более серьёзно то, что в копирующем конструкторе, во-первых, не уменьшается текущий ref_count ("SharedPtr a = new ..., SharedPtr b = new ...; a = b;" — память под a утекла), а во-вторых — ня проверяется, что чужой ref_count != nullptr (падаем когда пытаемся копировать перемещённый SharedPtr).

      А, operator=() нят. Тогда не течём, просто падаем.
      Ответить
      • > падаем когда пытаемся копировать перемещённый SharedPtr

        А копирование перемещённого говна разве не UB должно быть?
        Ответить
        • Во всей стандартной библиотеке у перемещённых объектов можно вызывать методы без предусловий.
          То есть я могу вызвать у вектора size() посмотреть, что там 0 и успокоится. Или вызвать push_back() и продолжить его использовать.

          Как в стандарте написано: valid, but unspecified state.
          Ответить
          • > Или вызвать push_back() и продолжить его использовать

            То есть у перемещенного вектора? Ну это пиздец какой-то. Надо руки отрывать за такое?
            Ответить
            • Не факт, что ты знаешь о том, что его кто-то переместил.
              Ответить
          • Забавно, мне казалось, что единственное, что можно вызвать у поехавшего объекта это десктрутор
            Ответить
            • lib.types.movedfrom:
              1#
              Objects of types defined in the C++ standard library may be moved from ([class.copy.ctor]). Move operations may be explicitly specified or implicitly generated. Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.
              2#
              An object of a type defined in the C++ standard library may be move-assigned ([class.copy.assign]) to itself. Unless otherwise specified, such an assignment places the object in a valid but unspecified state.
              Ответить
              • >valid but unspecified state.
                То есть это не UB, но в какое именно состояние они впали мы не знаем?
                Типа система в целом не сломалась, но они имеют правда быть в любом валидном состоянии?
                Ответить
                • Да, все инварианты ещё выполняются. То есть у вектора size, у которого нет предусловий, должен вернуть число, и если это число не 0, то 1й элемент существует и до него можно добраться. Если capacity > size, то push_back не переаллоцирует вектор и т.д.

                  В общем, после move мы должны считать объект свежеполученым и у нас нет никаких данных о его состоянии.
                  Ответить
                  • Лол, тогда у меня в коде есть сломанные классы (хотя это ведь требование тока для STL?).

                    В момент мува я опусташаю объект, и ставлю флажок, и дальше у него работает только десктрутор, а остальные методы вообще могут кинуть исключение, типа "инвалид стейт"

                    Эх
                    ------

                    Вообще хуйня: если у тебя класс в момент создания создает некую сущность, которую он передает в момент мува

                    Что ему делать-то потом? новую сущность создавать?

                    Трудновыполнимое требование. Хорошо, что я не пишу STL
                    Ответить
                    • Если этот флажок можно проверить и в документации к классу прописано предусловие к этим методам, то почему бы и нет. Но конструкторы/операторы присваивания стоит сделать работающими в любых случаях.
                      Ответить
                    • Ещё я не могу оставить ссылку на кишки нового объекта. Нужно либо аллоцировать под мувнутый объект массив, либо уметь работать с пустым указателем (когда 0 элементов в векторе).
                      Ответить
                • Я так понял если это вектор, то я имею право сделать его пустым, или оставить как было, или забить его значениями {6, 6, 6}.
                  Ответить
                  • Да, что тебе покажется более оптимальным.

                    Нужно отметить, что это касается стандартной библиотеки. Для своего кода ты можешь поставить предусловие "не был перемещён" для всех методов и радоваться жизни. Но следует обратить внимание на то, что может быть вызвано неявно. Обычно это деструктор, но если ты, скажем, перемещаешь объект из вектора, а потом он переаллоцируется, или из середины вектора удаляется значение, или его сортируют/применяют другой алгоритм то у твоего класса будет вызван копирующий/перемещающий конструктор/оператор присваивания. Ну и то, что использует конкретный алгоритм.
                    Ответить
                    • Постой-постой

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

                      Какой пиздец-то
                      Ответить
                      • Типа мувнуть туда-обратно?
                        Ответить
                        • Вектор принимает только мувабельные и копабельные объекты, и вообще он не дурак копирнуть объектца когда растет.

                          Потому пихать в него сложные объекты лучше по ссылке/указателю ИМХО.
                          А напрямую сувать только тривиально копируемую мелочь типа структуры о двух полях


                          Жабаоёбы-скриптушки о такой хуйне не думают, лол.
                          Вернуть из функции массив объектов, каждый из которых представляет живое TCP соединение?

                          говно вопрос!
                          Ответить
                          • В новых крестах вектор вполне способен работать с някопируемыми объектами (ня будет работать часть операций, вроде копирования самого вектора и push_back(), но в целом всё будет хорошо). Ему нужня только перемещение.
                            Ответить
                            • дык я и написал жыж
                              >мувабельные и копабельные объекты

                              А у листа какие требования?

                              Ладно, я научился читать:

                              T must meet the requirements of CopyAssignable and CopyConstructible.
                              Ответить
                              • > мувабельные и/или копабельные

                                У листа вообще только https://en.cppreference.com/w/cpp/named_req/Erasable, а остальные появляются по мере использования.
                                Ответить
                                • Вижу: до с11 они были копибл и асайнбл, а с 11 научились выбирать методы в зависимости от свойств
                                  Ответить
                          • Вопрос не в этом. Может ли вектор мувнуть объект куда-то, а потом что-то мувнуть на его место?
                            Ответить
                            • Кажется, что может. Вектор должен быть последовательным. Если места последовательного нету, то он может вообще всё подвинуть куда-то. А потом обратно. нет?
                              Ответить
                            • Да. Сделай erase из середины и вот тебе последовательно мувают обект в предыдущий, потом на его место мувают следующий.
                              Ответить
                      • > Я двинул объект из вектора

                        А ты считай, что std::move и мув-конструкторы -- это такая оптимизация, которая может и не примениться. Как RVO/NRVO.

                        Жить станет намного легче. И сразу понятно будет, когда надо вилкой чистить самому, а когда и так сойдёт.

                        Ну т.е. ты вполне можешь кидать invalid state из методов мувнутого объекта. С тем же успехом ты их можешь кинуть (и скорее всего кидаешь?) и из сконструированного по-умолчанию. Т.к. он тоже в не особо юзабельном состоянии.
                        Ответить
                        • > А ты считай, что std::move и мув-конструкторы -- это такая оптимизация, которая может и не примениться.

                          У меня в Си нет той хуйни, из-за которой такая оптимизация могла бы понадобиться. Именно поэтому я за Си.
                          Ответить
                          • У тебя в "си" все объекты trivially copyable.
                            Ответить
                            • У меня в Си нет "объектов" как в крестах. Есть только байтики, которые можно просто копировать. Поэтому я за Си.
                              Ответить
                              • А разве в си нету термина "объект"?
                                Вполне же есть, и означает он как раз кусочек памяти
                                Ответить
                                • Это не тот "объект" что в крестах, у которого какой-то там овнершип, конструкторы, деструкторы, raii и прочая дрисня. И к ООП "объект" из Си никакого отношения не имеет.
                                  Ответить
                                  • Так ООПшный обжект это расширение того сишного понятия "объект".

                                    В сишке у тебя есть объект int из 8 байт, и ты его туда-сюда копируешь.
                                    А при копировании ООП объекта будут еще коснтрукторы/деструкторы вызываться


                                    Овнершип в сишке тоже есть, на самом деле: если ты сделал malloc, то ты точно знаешь, когда нужно сделать free


                                    зы:
                                    object: region of data storage in the execution environment, the contents of which can represent values

                                    (ANSI C)
                                    Ответить
                                    • > Овнершип в сишке тоже есть, на самом деле: если ты сделал malloc, то ты точно знаешь, когда нужно сделать free

                                      Это не овнершип, это уже в голове программиста должно быть, что что-то где-то надо освободить. А можно и не освобождать, если это какая-то примитивная программа, которая допустим конвертит png в jpg используя какие-то либы, и потом завершается. ОСь сама приберет.
                                      Ответить
                                      • ИМЕННО ПОЭТОМУ Я ЗА ПХП.
                                        Ответить
                                      • > ОСь сама приберет
                                        *Грустные звуки embedded систем*

                                        Хотя в них и динамического выделения памяти обычно нет.
                                        Ответить
                                        • На эмбеддед системах я обычно статически память размечаю, и пофиг на malloc. Ну и можно всё прибирать по завершению треда, т.е. все поинтеры от malloc упихивать в какой-то стек, а потом его освободжать, если мы точно знаем, что эта malloc-нутая память используется только в контексте этого треда, и нигде больше. Вот тогда-то и появляется какое-то "владение", типа тред владеет этой памятью, и перед завершением он ее освободит всю.
                                          Ответить
                                          • Зачем вообще малок если у тебя на всей железке работает единственная программа?
                                            Ответить
                                            • > единственная программа

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

                                              Но это скорее оверлейки чем процессы.
                                              Ответить
                                            • > Зачем вообще малок если у тебя на всей железке работает единственная программа?

                                              Железка может уметь много чего делать, и ей может быть не нужно делать это одновременно (и памяти на одновременное делание всего у нее тоже нет). Поэтому железка может выделять память под хуйню№1, сделать хуйню№1, освободить память, выделить память под хуйню№2, сделать хуйню№2, освободить память. И эти хуйни№x может быть много, некоторые не сильно жрущие оперативку хуйни могут вполне работать одновременно, и они могут быть заранее неизвестны т.е. загружаться с внешнего флеш-накопителя или по блютузу какому-то скачиваться и устанавливаться, если это какая-то IoT-хуита. Так что для каких-то задач это вполне себе пригождается.
                                              Ответить
                                      • Это овнершип в голове у программиста же
                                        В крестах ты тоже можешь вручную это хендлить.

                                        Тащемто кресты же это как раз про то, чтоб автоматически делать то, что сишник делает вручную.
                                        Ответить
                                        • >Тащемто кресты же это как раз про то, чтоб автоматически делать то, что сишник делает вручную.

                                          В крестах через эту вашу RAII-хуйню можно далеко не все, что можно руками. И RAII это не какая-то мегауниверсальная хуйня на все случаи, это просто еще один способ управлять ресурсами. См. https://govnokod.ru/27175#comment624086
                                          Ответить
                                          • Совершенно верно. Серебрянной пули не существует;)

                                            >Допустим можно освобождать ресурсы в отдельном треде для освобождения ресурсов, и делать это во время простоев

                                            Не изобрети случайно ГЦ, пожалуйста
                                            Ответить
                                            • Ну это вариант для реалтаймовых систем. В конце срока жизни вместо освобождения ресурса, добавляем его в список ресурсов на освобождение, а отдельный тред их уже прибирает, когда сможет. При желании, такое можно и на крестах намутить.
                                              Ответить
                            • Вообще говоря нет. Просто сишник умный и сам знает когда структуру можно memcpy а когда нет.
                              Ответить
                              • Вот да. Царям виднее, куда что и как копировать.
                                Ответить
                              • а, ну да: если там дескриптор сокета, то наверное не нужно его копировать бездумно, и если там указатель, то нужно не сделать потом всем копиям free

                                Кстати, почему в "си" нельзя скопировать массив присваиваением?
                                Нельзя же?
                                Ответить
                                • > нельзя скопировать массив присваиваением

                                  Из-за автокаста в поинтер.

                                  Массивы существуют только как структура данных, а любое обращение к ним превращается в работу с поинтерами. Ну кроме sizeof.
                                  Ответить
                                  • Почти любое. Можно же sizeof массива взять.
                                    Ответить
                                  • Можно поинтер на какую-то питушню кастануть в поинтер на структуру в которой массив из байтиков конечного размера. А потом еще другой какой-то поинтер тоже так кастануть. И можно присваивать
                                    (но надо учесть всякую питушню с alingment и strict aliasing так что могут потребоваться доп. атрибуты)
                                    Ответить
                                    • > Можно

                                      Но это будет UB? Это же не структуры с одинаковым началом и не поля юниона.
                                      Ответить
                                      • > Но это будет UB?

                                        Думаю тут надо __attribute__ ((aligned (1), packed, may_alias)). Тогда UB не будет.
                                        Ответить
                                        • > __attribute__ ((aligned (1), packed, may_alias))
                                          И эти люди нядовольны std::move()!
                                          Ответить
                                  • Самая мерзкая хуйня в си.

                                    Почему было их не копировать как обычные структуры?
                                    Если я хочу передать массив в функцию по указателю, ну и писал бы &petuh или &petuh[0], не знаю.

                                    Вроде это сделали по аналогии с каким-то древним япом
                                    Ответить
      • > А, operator=() нят.

        В минимальной версии и без него достаточно. Оно ж просто не скомпилируется.
        Ответить
    • а std::shared_ptr уже отменили?
      Ответить
    • ya perepesala

      template<typename T>
      class SharedPtr {
          struct Ref {
              T* value;
              std::atomic_int count;
          };
          Ref* ref;
      
      public:
          SharedPtr() : ref(nullptr) {
          }
      
          explicit SharedPtr(T* value) {
              ref = new Ref;
              ref->value = value;
              ref->count = 1;
          }
      
          SharedPtr(const SharedPtr& other) {
              construct(other);
          }
      
          SharedPtr(SharedPtr&& other) {
              ref = other.ref;
              other.ref = nullptr;
          }
      
          ~SharedPtr() noexcept(noexcept(delete std::declval<T*>())) {
              destroy();
          }
      
          SharedPtr& operator =(const SharedPtr& other) {
              if (this == &other) {
                  return *this;
              }
      
              destroy();
              construct(other);
              return *this;
          }
      
          T& operator *() const {
              return *ref->value;
          }
      
          T* operator ->() const {
              return ref->value;
          }
      
          operator bool() const {
              return ref != nullptr;
          }
      
      private:
          void construct(const SharedPtr& other) {
              if (other.ref) {
                  ref = other.ref;
                  ref->count++;
              } else {
                  ref = nullptr;
              }
          }
      
          void destroy() {
              if (ref == nullptr) {
                  return;
              }
              auto count = --ref->count;
              if (count == 0) {
                  auto value = ref->value;
                  delete ref;
                  delete value;
              }
          }
      };


      Ну как?
      Ответить
      • > T& operator *() const

        Какой багор ))) А не, норм, тот конст внутри T.
        Ответить
      • Нябольшое замечание:
        ~SharedPtr() noexcept(noexcept(destroy()))
        void destroy() noexcept(noexcept(delete std::declval<T*>()))
        SharedPtr(SharedPtr&& other) noexcept // move-конструкторы и move-операторы присваивания должны быть noexcept
        Ответить
        • Я ня понямаю что это и зачем. Поэтому за "PHP".
          Ответить
      • А точно на счётчике нужна sequentially consistent сёмантика?
        Ответить
        • Хочешь затянуть его в омут увлекательный мир кэшей, синхронизации и барьеров памятиヽ(*⌒▽⌒*)ノ?
          Ответить
          • Да, но я сама не могу понять, какая именно сёмантика тут нужна...

            В самом shared_ptr и его Ref никаких гонок, по идее, нет, т.е. хватило бы relaxed'а.

            Но что делать с delete value? Сослаться на то, что клиент сам своё говно синхронизировать обязан?
            Ответить
            • Не знаешь какая семантика нужна? Ставь sequentially consistent, не ошибёшься. Пирфоманс потом подправишь, когда разберёшься.
              Ответить
              • Если перфоманс не нужен, можно на пхп писать. А то модели памяти какие-то, семантики... Голова пухнет.
                Ответить
            • Из MSCRT:
              bool _Incref_nz() noexcept { // increment use count if not zero, return true if successful
                  auto& _Volatile_uses = reinterpret_cast<volatile long&>(_Uses);
              #ifdef _M_CEE_PURE
                  long _Count = *_Atomic_address_as<const long>(&_Volatile_uses);
              #else
                  long _Count = __iso_volatile_load32(reinterpret_cast<volatile int*>(&_Volatile_uses));
              #endif
                  while (_Count != 0) {
                      const long _Old_value = _INTRIN_RELAXED(_InterlockedCompareExchange)(&_Volatile_uses, _Count + 1, _Count);
                      if (_Old_value == _Count) {
                          return true;
                      }
              
                      _Count = _Old_value;
                  }
              
                  return false;
              }
              Ответить
      • --ref->count;

        Та зачем ref копьём проткнул? (((
        Я бы лучше скобочки поставил, мало ли что.

        SharedPtr(const SharedPtr& other) {
                construct(other);
        }


        А я боюсь так делать в крестах, у меня постоянно конпелятор ругается на подобное.
        Ответить
        • > Та зачем ref копьём проткнул? (((

          uwu

          > А я боюсь так делать в крестах, у меня постоянно конпелятор ругается на подобное.

          owo
          Ответить
        • > А я боюсь так делать в крестах

          Ну да, очень легко позвать что-то виртуальное и напороться на UB с pure virtual call.
          Ответить
          • Я боюсь, потому что оно может сказать, что объект ещё не сконструирован до конца, и я сосну, но теперь мои страхи только укрепились.
            Ответить
            • > оно может сказать

              Если конпелятор что-то скажет -- эт хуйня, чего тут бояться... Но он скорее промолчит и вызовет метод родителя вместо твоего. Особенно забавно, когда он НЕ абстрактный и прога сразу не падает.
              Ответить
              • Ещё забавнее, когда прога вообще не падает. Но в ней происходит какая-то хуйня и ты не можешь понять, отчего.
                Ответить
      • if (this == &other) {
            return *this;
        }

        Дурно пахнущая пессимизация. Ненужное сравнение, кторое сработает дай бог раз за 1000 использований.
        Хотя в данном случае, если вдруг атомики оказались не lock-free, то возможно будет быстрее, чем стандартная альтернатива.
        Ответить
        • А как правильно проверить?
          Ответить
          • Забить и не проверять. Погугли copy-and-swap idiom.
            Ответить
            • Так ёбнется последний указатель на объект, если не проверить.
              Ответить
              • См. коммент [email protected'а] ниже, он там показал пример copy-and-swap. Последний указатель не ёбнется т.к. ты сначала его скопируешь.
                Ответить
          • Обычно стоит писать так, чтобы было пофиг на то, что передаётся и без проверок.

            К тому же в том коде нет strong exception safety. Утечек нет, но если что-то пойдёт не так при уничтожении старого значения при присваивании, то у тебя попортится старое значение, а присваивания не произойдёт. Ну и перемещающего присваивания нет.

            Во-первых ни один подход — не серебрянная пуля, об этом следует помнить. Иногда решение, которое прекрасно работало сотни раз, оказывается субоптимальным.

            Ну а во вторых, вот как может выглядеть оператор копирующего/перемещающего присваивания.
            SharedPtr& operator=(SharedPtr other) 
            {
                swap(*this, other);
                return *this;
            }
            Реализацию swap оставляю на самостоятельное изучение.
            Ответить
            • И так как я ленивая жопа, так выглядит мой перемещающий конструктор:
              SharedPtr(SharedPtr&& other) : SharedPtr() 
              {
                  swap(*this, other);
              }
              Ответить
              • Ленивая — потому что лень писать noexcept (≧◡≦)?
                Ответить
              • Блин, что-то не нравится мне это...
                // у меня был какой-то объект в p
                // и я его отдал другому объекту
                foo->bar = std::move(p);
                // теперь у меня в p есть неведомая хуйня, которую мне отдали взамен
                // я её конечно затру или разрушу, но всё равно как-то неинтутивно
                // особенно если там побочки в деструкторе
                Ответить
                • Там будет сконструированный по умолчанию объект. valid but unspecified state и всё такое.
                  Ответить
                  • > Там будет сконструированный по умолчанию объект
                    // у меня был объект в p и я его отдал
                    foo.bar = std::move(p);
                    // теперь у меня в p сконструированный по-умолчанию объект
                    // у меня был ещё один объект в q и я его тоже отдал
                    foo.bar = std::move(q);
                    // теперь у меня в q объект, который когда-то был в p
                    // не самое ожидаемое поведение, хоть и valid but unspecified
                    Ответить
                    • Там тоже объект по умолчанию. Оператор присваивания берёт по значению, а перемещающий конструктор отдаёт пустой объект.
                      Ответить
                      • А, всё, в операторе присваивания по значению. Тогда норм.

                        Мой объект просто мувается в аргумент, а старое значение разрушается на выходе из оператора.
                        Ответить
                    • operator=() принимает SharedPtr по знячению. В q будет сконструированный по-умолчанию объект.
                      Ответить
                    • Кстати, я тут подумал. Чтобы гарантировать noexcept move assignment (которое иногда хочет видеть стандартная библиотека для некоторых оптимизаций) для классов с деструкторами, которые могут кидать исключения, как раз можно схитрить и воспользоваться noexcept swap чтобы отдать кишки левого объекта правому — раз мы ничего сейчас не уничтожаем, то перемещение будет noexcept. А исключение вылетит потом, когда будем уничтожать правый объект. Удачи в дебаге.
                      Ответить
                      • Хорошо что у меня всей этой хуйни в Си нет. Не нужно забивать голову какой-то внутриязыковой парашей, и можно сосредоточиться на самом программировании.
                        Ответить
                        • > сосредоточиться на самом программировании

                          Когда-нибудь и я уйду в монахи... Но пока кресты вполне устраивают.
                          Ответить
                          • Меня не устраивают. Я не хочу себе мозг забивать говном, которое только в этом говноязыке имеет какой-то смысл. Потому что если ебать себе мозг таким ублюдским говном типа крестов, оно там прочно укореняется и ты уже начинаешь мыслить какими-то говнокатегориями этого языка, типа "так бля конструктор-деструктор хуё-моё, тут у нас rvalue копирующее noexcept кококо" тьфублядьнахуй... какие-то дегроды придумали какой-то дегродский говноязык с кучей ебанутых дегенератских правил и кучей исключений к каждому говноправилу.
                            https://habr.com/ru/post/497114/

                            > Кто я такой? Я программист на плюсах. Я пишу код на плюсах две трети жизни. Это действительно стало огромной частью моей самоидентификации. Почему я претендую на синиора? Потому что я успел отстрелить себе миллион ног и натаскать свою нейросеть в черепушке определять, какая нога отстрелена на этот раз, по отсвету дульной вспышки. Потому что все интервью, касающиеся языка, я проходил, как тут говорят, with flying colors. Потому что я могу даже не готовиться к интервью, и всё равно его пройду. Потому что я могу показать особо интересные куски кода на гитхабе, и для меня скипнут как минимум телефонное интервью, а иногда скипали и техническое собеседование, разговаривая только о жизни или в лучшем случае о дизайне систем, релевантных для места работы.
                            Ответить
                            • > А так придётся выкидывать весь этот опыт в корзину и начинать почти с нуля. Ну, да, у меня есть какое-то там системное мышление, я дизайнил компиляторы, я дизайнил распределённые системы с противоречивыми требованиями, это, наверное, тоже кусок синьористости, но если меня разбудить ночью и спросить «почему ты претендуешь на синьора», я отвечу «потому что сиплюсплюс». Ко мне обращаются коллеги, когда там какой-то адовый баг или что-то не работает или надо что-то сделать на темплейтах или хорошее код-ревью бы. Это давно уже так — помню, ещё где-то на третьем курсе, когда я отрабатывал пропуски по физкультуре заплывами в бассейне, ко мне подплыл какой-то чувак и спросил: «а это ты 0xd34df00d? у меня тут вопрос по boost…»
                              Ответить
                            • Какое самолюбование :))
                              (у автора конечно, не у тебя)
                              Ответить
                          • Вообще, знания и навыки можно условно разделить на две категории.

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

                            А есть знания, которые о какой-то созданной человеке хуитени, в которых ничего фундаментально-значимого нет. Типичный пример - инструкция использования какого-то сложного прибора с кучей кнопочек и переключателей. И если тебе в какой-то момент дадут другой прибор от другого производителя с другими кнопочками и переключателями, твои задротские навыки использования того прибора окажутся нахуй не нужны, понимаешь? Вот знания крестоговна - это как раз из этой области.
                            Ответить
                            • Ну так можно про любой язык сказать: знание стандартной библиотеки сей тоже тебе станет не нужно, если ты уёдешь писать на php
                              Ответить
                              • В стандартной библиотеке Си почти нихуя знать не надо, в отличии от крестов.
                                Ответить
                                • Вообще знания языка (даже такого сложного, как кресты) -- ничто, по сравнению со фундаментальщиной типа алгоритмов, структур данных, дискретки, архитектуры компьютеров, компиляторов и операционных систем, итд.

                                  Не понимаю, зачем о них волноваться
                                  Ответить
                                  • > Вообще знания языка (даже такого сложного, как кресты) -- ничто, по сравнению со фундаментальщиной типа алгоритмов,

                                    Ну если сопоставить размер книги по крестам от Страуструпа с размером книги Танненбаума по ОС... вполне сопоставимы.

                                    Видимо забивать мозг этой крестохуйней нужно в сопоставимой степени, чтобы считаться сеньор-крестоговно-девелопером
                                    Ответить
                                    • Thinking Java Брюсса Эккеля толще чем та книга Страуструпа по крестам.

                                      Почему мы не слышим таких статей от джава программистов?
                                      Ответить
                                      • Ну вообще джава проще, конечно. Даже последняя. Толщина там скорее всего из-за того, что в одной книжке смешали не только язык, но и паттерны, многопоточность, какие-то практические применения и т.п.
                                        Ответить
                                        • Ну вот видишь: размер книжки не важен:)

                                          У джавы довольно развесистая стандартная либлиотека, особенно если включить в неё JavaEE. Может быть книжка большая потому.
                                          Ответить
                            • Циклы, структуры данных, рекурсия, оценки сложности и т.п. -- это тоже не какие-то там фундаментальные законы физики, это знания о конкретной хуитени, придуманной человеком: цифровой вычислительной машине, которая пошагово выполняет твой код.

                              Да, это сейчас популярно. Но вангую что лет через 50-100 это всё уйдёт на помойку, как туда ушли знания об аналоговых ЭВМ.

                              З.Ы. Пригодится ли тебе умение писать циклы и рекурсию для вычислений на FPGA? Не думаю, мне первое время даже мешало.
                              Ответить
                              • Рекурсия есть и без ЭВМ. Например, рекурсивное деление клеток в организме человека (и не только человека).

                                Циклы тоже можно и без ЭВМ. Например, если тебе надо забить 200 гвоздей в ряд с расстоянием в 1 см между каждым, можно представить это себе как цикл, забить гвоздь по смещению 0 см влево относительно этой координаты, потом забить гвоздь по смещению 1 см влево относительно этой координаты, и так далее.

                                Циклы и рекурсия ближе к фундаментальной математике, чем к каким-то цифровым машинам. Они и не на цифровых машинах могут.
                                Ответить
                              • > З.Ы. Пригодится ли тебе умение писать циклы и рекурсию для вычислений на FPGA?

                                Ну если тебе надо экономить ячейки, и скорость не важна, вполне можно некую хрень саму на себя как-нибудь зациклить в схеме из логических элементов. Попробуй bigint умножение на FPGA реализовать для больших чисел.
                                Ответить
                                • > зациклить

                                  Но в коде это не выглядит как цикл! Умение писать циклы на традиционных языках тут вообще только мешает понять новую концепцию.

                                  З.Ы. Это ближе к чему-то функциональному в духе map/reduce.
                                  Ответить
                                  • Не выглядит, но там этот цикл есть. Можно сделать DSL который может тебе обычный цикл развернуть в какую-то такую питушню.

                                    Или можно хаскель взять какой-нибудь, там циклов тоже нет, и что?
                                    Ответить
                                  • Как и вообще умение пистать на мейнстримном императивном говне образца четвертой джавы очень сильно мешает писать на чем-то функциональном, лол
                                    Ответить
                                    • > очень сильно мешает писать

                                      Это факт, вон в факторио у программистов всегда первый вопрос "а как мне запилить if или for на кобенаторах?" Ну т.е. если ты всю жизнь мыслишь концепциями циклов и условий, то очень сложно принять что-то новое.
                                      Ответить
                                      • Есть и обратная проблема. Школьника на уроках алгебры обучили функциям, а потом на информатике показали рекурсивную процедуру на паскале, и он всё время пытается понять, чему "равно" proc(42).

                                        Видел такое

                                        Хотя императивщину, наверное, проще понять: всё так наш мир императивен
                                        Ответить
                                        • > Видел такое

                                          Так Эрланг появился. Джо Армстронгу (RIP) тоже подобный анскилл не понравился.
                                          Ответить
                                        • > наш мир императивен

                                          Скажи это электронам и прочим фотонам.
                                          Ответить
                                          • За пределами ньютоновской физики происходит какая-то непонятная хуйня, которую большинство обычных людей всё равно не понимает
                                            Ответить
                                            • тут тупое рашкообразование подложило хрюкни
                                              Ответить
                              • Рекурсия вполне себе из математики пришла, ещё до всяких `ЭВМ'. Была и будет, пока есть функции. Вполне себе фундаментальная питушня.
                                Ответить
                                • > пока есть функции

                                  Выдуманная человеком хуйня, как и вся математика.

                                  Не факт, что какие-нибудь иноплане-тянки с альфы Центавра придут точно к таким же концепциям, даже если физика у них та же самая.
                                  Ответить
                                  • > Не факт, что какие-нибудь иноплане-тянки с альфы Центавра придут точно к таким же концепциям, даже если физика у них та же самая.

                                    Та же химия/физика описывается во многих местах через рекурсию, вполне естественным образом. Всякие динамические системы там с диффурами есть. Допустим состояние системы в момент времени t2 зависит от состояния системы в момент времени t1 и у нас есть фукнция отображения state(t2) := do_transform(state(t1)) и тут кстати можно попробовать найти неподвижную точку, когда некий state(x-1) == do_transform(state(x-1)) т.е. когда состояние системы не меняется от времени.

                                    Физика нашей реальности такой питушней хорошо описывается, и поэтому совершенно естественно, что такой формализм будет придуман для ее описания

                                    Ты же не будешь утверждать, что такие инопланетянки могут построить космические корабли, но не будут знать, что 1+1=2, верно?
                                    Ответить
                                    • Есть где-то пруф, что нет каких-то других формализмов, которые тоже хорошо описывают реальность?
                                      Ответить
                                      • Предлагаю для начала придумать формальный метод, которым мы будем доказывать, что эти формализмы принципиально разные. А так же доказать, что наш формальный метод отличать формализм от формализма сам не является хуйней.
                                        Ответить
                                        • И чтобы гомоиконность формализмов!
                                          Ответить
                                      • Функция — это наиболее эффективный алгоритм зожатия данных. Вместо хранения в памяти миллиона точек, составляющих окружность, можно хранить уравнение кривой, и вместо миллиона извилин тебе потребуется две с половиной.
                                        Если законы физики у инопланетянок такие же, то и эволюционный отбор у них работает примерно так же, т.е. стремится на всём, включая размер мозга, сэкономить.
                                        Ответить
                                        • хуй ня, мозг просто чудовищно неэкономичен с целой кучей бутстрапов и легасей
                                          Ответить
                • В > : SharedPtr() магия.
                  Ответить
            • Я реализовал!
              template <typename T>
              void swap(T* a, T* b) noexcept
              {
                c = new T;
                d = new T;
                c = a;
                d = b
                a = d;
                b = c;
                delete c;
              }
              Ответить
    • как решена проблема цикличных ссылок в смарт поинтерах?
      Ответить
      • В STL — через https://en.cppreference.com/w/cpp/memory/weak_ptr. А 3_darу её ещё предстоит решать (* ^ ω ^).
        Ответить
        • > Предстоит решать

          Пиздец (( Пока по твоей ссылке не понял что это и как это хуетой пользоваться.
          Ответить
          • Допустим, у тебя есть кнопка и её слушатель.
            Кнопка знает про слушателя, а слушатель -- про кнопку.

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

            Для решения этой проблемы в ЯП с RC есть weak ссылы/указатели. Они буквально означают "пусть тот, на кого у меня ссылка , умрет. Мне похуй. Я как-нить переживу".

            У кнопки на слушателя shared_ptr (а больше ни у кого). Это значит, что когда кнопка умрет, должен будет умереть и слушатель.

            У слушателя на кнопку weak_ptr. Перед обращением к нему слушатель проверяет её метод "expired". Если она expired, то ссылка невалидна.

            Ключом к понимаю weak_ptr является вот этот метод:
            https://en.cppreference.com/w/cpp/memory/weak_ptr/expired
            Ответить
      • Во всех средах с RefCounting она решена через слабые указатели.
        Вот пример из Swift
        https://www.programmersought.com/article/1272854504/
        Ответить
      • >как решена проблема цикличных ссылок в смарт поинтерах?

        Хуёво она решена (если это вообще можно назвать решением).
        См. https://govnokod.ru/27340#comment621591
        Ответить
        • нормально она решена:
          >то мы еще должны заранее знать
          да, ты должен всегда понимать кто на что ссылается. В некоторых средах на это есть гайдлайны.

          Не хочется руками разгребать циклы -- кушайте ГЦ. Или есть еще более красивые варианты?
          Ответить
          • > да, ты должен всегда понимать кто на что ссылается. В некоторых средах на это есть гайдлайны.

            А если у меня просто граф из какой-то хуйни, который хуй пойми как перестраивается в процессе работы, и если я там буду придумывать, какая из хуитеней на что когда и как указывает как weak_ptr, а какая как обычный shared_ptr то это просто сраный пиздец получится. Придумывать еще какую-то ебанутую логику, что если такой-то shared_ptr переткнулся с той дрисни на ту дрисню в каком-то говнографе, то вот эта shared_ptr дрисня будет уже weak_ptr. Это будет просто адовый сраный пиздец, и работать это будет вероятно даже медленней, чем GC т.к. надо по этим говносвязям бегать и переставлять weak_ptr на shared_ptr и наоборот.

            Так что в целом эта проблема, имхо, никак не решена. Заменить этой хуйней обычный GC тупо нельзя
            Ответить
            • А как бы ты её на сишке решал?

              Если в твоем графе тебе понадобится не только память чистить, но и закрывать ресурсы, то тебе всё равно придется строить граф на бумажечке, и понять, где папа, а где сын.

              Если из всего множества программ выкинуть те, в которых:
              * Граф очевиден
              * Граф всё равно нужно понимать чтобы освобождать ресурсы
              * Можно вообще ничего не освобождать, а сразу выделить память, и жить с ней до конца програмы

              ..то останутся случаи, для которых хорошо подходит GC. Их не так и много
              Ответить
              • У меня в сишке на контроллерах такой дрисни тупо нет, чтоб ее решать. У меня есть идея, как ее можно было бы решить через механизм, чем-то похожий на хуйню с shared_ptr/weak_ptr, но это слишком дохуя описывать. Может быть потом изложу мысль.

                Суть там в https://en.wikipedia.org/wiki/Hypergraph
                Ответить
            • В языках с GC эта проблема тоже до конца не решена, на самом деле. GC решает её только для памяти, т.е. в нодах графа не может быть каких-то файловых дескрипторов и прочих ресурсов. Иначе один хер приходится перехуячивать граф в дерево или лес, как и для RC.
              Ответить
              • Да и для памяти он решает её лукаво.

                Если ты будешь делать вид, что память тебя не интересует, то она утечет. Именно потому в жабке тоже есть SoftReference и WeakReference. Абстракция протекла, и даже тупой жабаобезъяне пришлось понимать , что куча -- не бездонная корова, чтоб туда срать
                Ответить
              • У нас было 2 дескриптора, завёрнутых в акторы, 75 портов, линки, мониторы, io protocol, целое множество `with' всех сортов и расцветок. Не то чтобы это был необходимый запас для управления ресурсами, но если начал собирать дурь, становится трудно остановиться. Единственное, что вызывало у меня опасение — это lazy IO. Ничто в мире не бывает более беспомощным, безответственным и порочным, чем ленивое освобождение ресурсов. Я знал, что рано или поздно мы перейдем и на эту дрянь.
                Ответить

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