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

    +19

    1. 1
    2. 2
    3. 3
    4. 4
    Class1* c1 = (Class1*)malloc(sizeof(Class1)*N);
    Class2* c2 = (Class2*)malloc(sizeof(Class2)*N);
    for (long i = 0; i < N; i++) c1[i] = Class1();
    for (long i = 0; i < N; i++) c2[i] = Class2();

    Рассказать ему про new[] / delete[]?

    Запостил: runewalsh, 29 Ноября 2012

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

    • Расскажите лучше про in place инициализацию:
      // выделяем пул под будущие объекты
      T *pool = (T*)malloc(sizeof(T)*poolSize);
      
      // когда нужно выделить объект в пуле, вызываем
      T *p = new(pool+currentIndex) T(some, params);
      
      // чтобы вернуть объект в пул вызываем
      p->~T();
      
      // по завершении работы с пулом
      free(pool);
      Эта хреновина может выделять и освобождать объекты за O(1), в отличие от кучи, где это время недетерминированно. В некоторых, правда не слишком часто встречающихся, случаях такой подход может пригодиться.
      Ответить
      • Почему через malloc/free, а не operator new/operator delete?
        Ответить
        • Во-первых - чтобы код был ближе к коду ОП'а.
          Во-вторых - чтобы подчеркнуть низкоуровневость творимого здесь хаоса.
          В третьих - потому что так короче чем (T*)new char[sizeof(T)*poolSize], и уж точно короче и понятней, чем (T*)operator new[](sizeof(T)*poolSize), которые один хрен вызовут тот же самый malloc.

          P.S. Предвижу следующий вопрос - "почему сишный каст?".
          Ответить
          • НЕ факт, что вызовут malloc. Сишный каст-то без проблем, хотя static_cast скорее всего был бы предпочтительнее.
            Ответить
            • > НЕ факт, что вызовут malloc.
              Да, и это еще одна причина использовать тут malloc - даже если кто-то перегрузил operator new[](size_t), код не будет им пользоваться ;)

              P.S. Можно и системнозависимыми средствами в духе VirtualAlloc страничку выделить, если задача того требует. Или даже в заmmap'ленном куске организовать этот пул... Хотя и operator new[]() имеет полное право на жизнь, все зависит от задачи.

              Про static_cast согласен.
              Ответить
              • > даже если кто-то перегрузил operator new[](size_t), код не будет им пользоваться ;)

                Да конечно. Только стандарт тоже не гарантирует, что будет вызван malloc. С тем, что все зависит от задачи абсолютно согласен.
                Ответить
                • > Только стандарт тоже не гарантирует, что будет вызван malloc.
                  Согласен. Сейчас даже фразу об этом в нем нашел: Whether the attempt involves a call to the Standard C library function malloc is unspecified.
                  Ответить
                  • показать все, что скрытотолько питушки могли придумать стандарт, состоящий из одних UB
                    .
                        &
                    ( ^ ◊ ^ )
                    Ответить
                    • Unspecified это абсолютно нормально.
                      Ответить
                    • Где здесь UB, guest?
                      Здесь сказано только о том, что new может не вызывать malloc, а пользоваться другими механизмами.
                      Ответить
    • > Рассказать ему про new[] / delete[]?

      Расскажите, что он вызывает operator= для неинициализированных объектов, у которых даже конструктор не вызывался.
      Ответить
      • Самое печальное тут то, что оно не упадет, если оператор присваивания не юзает старые значения, и нет виртуальных функций.
        Ответить
        • Причем в самом Class1 может ничего хитрого и не быть, а вот в его полях... string какой-нибудь и все.
          Ответить
          • Да вот самое хреновое, если ничего не упадет. И человек подумает, что так делать можно.
            Ответить
    • Лутше раскажите ему про memcpy вместо конструктора копирования - раз уж такой содомит растет.
      Ответить
      • class Test {
        public:
            Test() {
                memset(this, 0, sizeof(*this));
            }
            Test(const Test & other) {
                memcpy(this, &other, sizeof(*this));
            }
            bool operator == (const Test & other) const {
                return !memcmp(this, &other, sizeof(*this));
            }
            Test & operator = (const Test & other) {
                memcpy(this, &other, sizeof(*this));
            }
        };
        Ответить
        • Ну и что?
          Ответить
        • Если в классе нет виртуальных функций и не нужно глубокое копирование, то даже будет работать =)
          Ответить
          • Хм, а почему указатель на талицу виртуальных функций таким макаром не переносится? Ведь по идее он есть в каждом экзепляре класса.
            http://ideone.com/E4U378
            Ответить
            • > Ведь по идее он есть в каждом экзепляре класса.
              Не в каждом. Только в тех, где есть хотя бы 1 виртуальный метод. По этой причине, кстати, RTTI не пашет на таких классах, и static_cast<T*>(a) может вернуть другой адрес даже без множественного наследования.

              А виртуальные функции тут нельзя из-за memset'а, который уничтожит указатель на таблицу.

              P.S. Не помню, пишется ли этот указатель перед конструктором или после... Надо пошариться в стандарте.
              Ответить
            • В стандарте не нашел, видимо плохо ищу. Но по крайней мере в gcc указатель инициализируется перед конструктором. Поэтому memset его запорет.

              http://ideone.com/Zi6Osl
              Ответить
              • в студии также
                Ответить
                • Но проблема легко обходится по стандарту созданием потомка:
                  http://ideone.com/zuoO3y
                  Ответить
                  • Создавать потомков - это хорошо
                    Ответить
                    • Каждый программист должен создать потомка, реализовать дерево и построить домашнюю страничку.
                      Ответить
              • Боюсь ошибиться в причинах-следствиях.
                Но мне кажется, что в стандарте ничего не прописано о порядке инициализации vptr и именно поэтому вызов виртуальных функций из конструктора не есть хорошо.
                Ответить
                • Что за порядок инициализации vptr? Из конструктора вызывать виртуальные функции не стоит т.к. экземпляр дочернего класса еще не создан.
                  Ответить
                  • Наверно не корректно выразился:
                    под фразой "порядок инициализации" я имел ввиду "перед конструктором или после него"

                    Да, и с формальной точки зрения. Я - базовый класс, у меня есть свой vptr и мне пофиг из какого производного класса я создаюсь. Так что аргумент " экземпляр дочернего класса еще не создан" не подходит.
                    Ответить
                    • Представьте, что виртуальная функция использует поля класса. Если вызывать ее в конструкторе базового класса, то поля дочернего еще не будут инициализированы.
                      Ответить
                      • Еще проблема есть - дочерние классы еще не прописывали свою таблицу, поэтому если вызвать виртуальный метод из конструктора - максимум, что случится - вызовется метод текущего класса или предка. А это скорее всего не то, что хотел программист.
                        Ответить
                        • Просто конструкторы вызываются строго в порядке от базового к производному классу. А вот с инициализацией vptr не все так однозначно. Т.е. в первом случае проблема есть всегда, а во втором - может быть, а может и не быть.
                          Ответить
                          • Member functions, including virtual functions (10.3), can be called during construction or destruction (12.6.2). When a virtual function is called directly or indirectly from a constructor (including from the mem-initializer for a data member) or from a destructor, and the object to which the call applies is the object under construction or destruction, the function called is the one defined in the constructor or destructor’s own class or in one of its bases, but not a function overriding it in a class derived from the constructor or destructor’s class, or overriding it in one of the other base classes of the most derived object (1.8). If the virtual function call uses an explicit class member access (5.2.5) and the object-expression refers to the object under construction or destruction but its type is neither the constructor or destructor’s own class or one of its bases, the result of the call is undefined.

                            Т.е. виртуальный вызов из конструктора или деструктора делать можно, и работает он так, как будто от текущего класса ничего не порождено. Что в общем то логично. И получается, что vptr текущего класса запихивается непосредственно перед вызовом его конструктора, а откатывается на родительский после выхода из деструктора (иначе стандарт бы указал, что вызов виртуалок из конструктора/деструктора это UB).

                            P.S. Но т.к. при этом конструктор еще не доработал, часть полей могут быть недоинициализированными, поэтому так делать нежелательно.
                            Ответить

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