Кресты / Говнокод #27391 Ссылка на оригинал

0

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  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 3_dar, (Updated )

Комментарии (198) 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 утёк.
            Ответить
              • Это всё не учитывая, что одновременная модификация count приводит к гонке и может испортить значение.

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

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

                    Защищать доступ к содержащемуся объекту — не твоя задача, об этом голова должна болеть у пользователя. Хотя можно сделать какой-нибудь shared_protected_resource, где ты будешь явно забирать ресурс.
                    Ответить
                  • Если ты хочешь, чтобы пирфоманс был подобен ПХП, то можно просто всё закрывать мютексом при любой операции. А так желательно, чтобы указатель был бы 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.
                            Ответить
                        • Вопрос не в этом. Может ли вектор мувнуть объект куда-то, а потом что-то мувнуть на его место?
                          Ответить
                          • Кажется, что может. Вектор должен быть последовательным. Если места последовательного нету, то он может вообще всё подвинуть куда-то. А потом обратно. нет?
                            Ответить
                          • Да. Сделай erase из середины и вот тебе последовательно мувают обект в предыдущий, потом на его место мувают следующий.
                            Ответить
                    • > Я двинул объект из вектора

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

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

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

                        У меня в Си нет той хуйни, из-за которой такая оптимизация могла бы понадобиться. Именно поэтому я за Си.
                        Ответить
                          • У меня в Си нет "объектов" как в крестах. Есть только байтики, которые можно просто копировать. Поэтому я за Си.
                            Ответить
                            • А разве в си нету термина "объект"?
                              Вполне же есть, и означает он как раз кусочек памяти
                              Ответить
                              • Это не тот "объект" что в крестах, у которого какой-то там овнершип, конструкторы, деструкторы, 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.
                                Ответить
                                • Можно поинтер на какую-то питушню кастануть в поинтер на структуру в которой массив из байтиков конечного размера. А потом еще другой какой-то поинтер тоже так кастануть. И можно присваивать
                                  (но надо учесть всякую питушню с alingment и strict aliasing так что могут потребоваться доп. атрибуты)
                                  Ответить
                                  • > Можно

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

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

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

                                  Вроде это сделали по аналогии с каким-то древним япом
                                  Ответить
                              • > Кстати, почему в "си" нельзя скопировать массив присваиваением?

                                Можно присваиванием скопировать структуру с массивом внутри. Массив тогда тоже скопируется
                                Ответить
                                • угу, и передать тоже так можно.

                                  Кстати, если я заверну массив в структуру, то получу нормальный хороший удобный тул практически с нулевым оверхедом, не?
                                  почти как std:array в твоем любимом языке
                                  Ответить
    • > А, operator=() нят.

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

          В самом 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;
            }
            Ответить
        • Да, но я сама не могу понять, какая именно сёмантика тут нужна...

          В самом 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
        Ответить
      • > Та зачем ref копьём проткнул? (((

        uwu

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

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

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

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

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

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

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

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

          Ну а во вторых, вот как может выглядеть оператор копирующего/перемещающего присваивания.
          SharedPtr& operator=(SharedPtr other) 
          {
              swap(*this, other);
              return *this;
          }
          Реализацию swap оставляю на самостоятельное изучение.
          Ответить
          • Я реализовал!
            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;
            }
            Ответить
          • Я реализовал!
            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;
            }
            Ответить
        • Обычно стоит писать так, чтобы было пофиг на то, что передаётся и без проверок.

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

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

          Ну а во вторых, вот как может выглядеть оператор копирующего/перемещающего присваивания.
          SharedPtr& operator=(SharedPtr other) 
          {
              swap(*this, other);
              return *this;
          }
          Реализацию swap оставляю на самостоятельное изучение.
          Ответить
          • И так как я ленивая жопа, так выглядит мой перемещающий конструктор:
            SharedPtr(SharedPtr&& other) : SharedPtr() 
            {
                swap(*this, other);
            }
            Ответить
            • Блин, что-то не нравится мне это...
              // у меня был какой-то объект в 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
                  // не самое ожидаемое поведение
                  Ответить
                • > Там будет сконструированный по умолчанию объект
                  // у меня был объект в p и я его отдал
                  foo.bar = std::move(p);
                  // теперь у меня в p сконструированный по-умолчанию объект
                  // у меня был ещё один объект в q и я его тоже отдал
                  foo.bar = std::move(q);
                  // теперь у меня в q объект, который когда-то был в p
                  // не самое ожидаемое поведение, хоть и valid but unspecified
                  Ответить
                  • Там тоже объект по умолчанию. Оператор присваивания берёт по значению, а перемещающий конструктор отдаёт пустой объект.
                    Ответить
                  • operator=() принимает SharedPtr по знячению. В q будет сконструированный по-умолчанию объект.
                    Ответить
            • Блин, что-то не нравится мне это...
              // у меня был какой-то объект
              // и я его отдал другому объекту
              foo->bar = std::move(p);
              // теперь у меня есть неведомая хуйня, которую мне отдали взамен
              // я её конечно затру или разрушу, но всё равно как-то неинтутивно
              // особенно если там побочки в деструкторе
              Ответить
            • Блин, что-то не нравится мне это...
              // у меня был какой-то объект в 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, верно?
                                  Ответить
                                  • Есть где-то пруф, что нет каких-то других формализмов, которые тоже хорошо описывают реальность?
                                    Ответить
                                    • Предлагаю для начала придумать формальный метод, которым мы будем доказывать, что эти формализмы принципиально разные. А так же доказать, что наш формальный метод отличать формализм от формализма сам не является хуйней.
                                      Ответить
                                    • Функция — это наиболее эффективный алгоритм зожатия данных. Вместо хранения в памяти миллиона точек, составляющих окружность, можно хранить уравнение кривой, и вместо миллиона извилин тебе потребуется две с половиной.
                                      Если законы физики у инопланетянок такие же, то и эволюционный отбор у них работает примерно так же, т.е. стремится на всём, включая размер мозга, сэкономить.
                                      Ответить
                                      • хуй ня, мозг просто чудовищно неэкономичен с целой кучей бутстрапов и легасей
                                        Ответить
          • Я реализовал!
            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;
            }
            Ответить
  • как решена проблема цикличных ссылок в смарт поинтерах?
    Ответить
  • как решена проблема цикличных ссылок в смарт поинтерах?
    Ответить
      • > Предстоит решать

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

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

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

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

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

          Ключом к понимаю weak_ptr является вот этот метод:
          https://en.cppreference.com/w/cpp/memory/weak_ptr/expired
          Ответить
      • нормально она решена:
        >то мы еще должны заранее знать
        да, ты должен всегда понимать кто на что ссылается. В некоторых средах на это есть гайдлайны.

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

          А если у меня просто граф из какой-то хуйни, который хуй пойми как перестраивается в процессе работы, и если я там буду придумывать, какая из хуитеней на что когда и как указывает как 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. Ничто в мире не бывает более беспомощным, безответственным и порочным, чем ленивое освобождение ресурсов. Я знал, что рано или поздно мы перейдем и на эту дрянь.
              Ответить

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

Помни, guest, за тобой могут следить!

    А не использовать ли нам bbcode?


    8