Разное поведение для разных размеров в C ++ (исходный код Firebreath) - PullRequest
1 голос
/ 31 октября 2011

Я сталкиваюсь с запутанным вопросом, когда прохожу исходный код Firebreath ( src / ScriptingCore / Variant.h )

    // function pointer table
    struct fxn_ptr_table {
        const std::type_info& (*get_type)();
        void (*static_delete)(void**);
        void (*clone)(void* const*, void**);
        void (*move)(void* const*,void**);
        bool (*less)(void* const*, void* const*);
    };

    // static functions for small value-types 
    template<bool is_small>
    struct fxns
    {
        template<typename T>
        struct type {
            static const std::type_info& get_type() { 
                return typeid(T); 
            }
            static void static_delete(void** x) { 
                reinterpret_cast<T*>(x)->~T(); 
            }
            static void clone(void* const* src, void** dest) { 
                new(dest) T(*reinterpret_cast<T const*>(src)); 
            }
            static void move(void* const* src, void** dest) { 
                reinterpret_cast<T*>(dest)->~T(); 
                *reinterpret_cast<T*>(dest) = *reinterpret_cast<T const*>(src); 
            }
            static bool lessthan(void* const* left, void* const* right) {
                T l(*reinterpret_cast<T const*>(left));
                T r(*reinterpret_cast<T const*>(right));

                return l < r;
            }
        };
    };

    // static functions for big value-types (bigger than a void*)
    template<>
    struct fxns<false>
    {
        template<typename T>
        struct type {
            static const std::type_info& get_type() { 
                return typeid(T); 
            }
            static void static_delete(void** x) { 
                delete(*reinterpret_cast<T**>(x)); 
            }
            static void clone(void* const* src, void** dest) { 
                *dest = new T(**reinterpret_cast<T* const*>(src)); 
            }
            static void move(void* const* src, void** dest) { 
                (*reinterpret_cast<T**>(dest))->~T(); 
                **reinterpret_cast<T**>(dest) = **reinterpret_cast<T* const*>(src); 
            }
            static bool lessthan(void* const* left, void* const* right) {
                return **reinterpret_cast<T* const*>(left) < **reinterpret_cast<T* const*>(right);
            }
        };
    };

    template<typename T>
    struct get_table 
    {
        static const bool is_small = sizeof(T) <= sizeof(void*);

        static fxn_ptr_table* get()
        {
            static fxn_ptr_table static_table = {
                fxns<is_small>::template type<T>::get_type
                , fxns<is_small>::template type<T>::static_delete
                , fxns<is_small>::template type<T>::clone
                , fxns<is_small>::template type<T>::move
                , fxns<is_small>::template type<T>::lessthan
            };
            return &static_table;
        }
    };

Вопрос в том, почему реализация статических функцийдля больших типов значений (больше, чем void *) отличается от маленьких.

Например, static_delete для малого типа значения просто вызывает деструктор для экземпляра T, а для большого типа значенияиспользовать «удалить».

Есть какой-то трюк?Заранее спасибо.

Ответы [ 3 ]

2 голосов
/ 31 октября 2011

Что говорит внутренняя документация?Если автор не задокументировал это, он, вероятно, не знает себя.

Судя по коду, интерфейс для маленьких объектов отличается от интерфейса для больших объектов;указатель, который вы передаете для маленького объекта, является указателем на сам объект, а указатель, который вы передаете для большого объекта, является указателем на указатель на объект.

Автор, похоже, не очень хорошо знает C ++ (и я бы не стал использовать любой подобный код).Например, в move он явно уничтожает объект, а затем присваивает ему объект: это гарантированное неопределенное поведение и, вероятно, не будет надежно работать ни для чего, кроме простейших встроенных типов.Также различие маленьких и больших объектов в значительной степени не имеет значения;некоторые «маленькие» объекты могут быть довольно дорогими для копирования.И, конечно же, учитывая, что все здесь является шаблоном, нет абсолютно никакой причины использовать void* для чего-либо.

2 голосов
/ 31 октября 2011

Похоже, что Firebreath использует выделенный пул памяти для своих маленьких объектов, в то время как большие объекты обычно размещаются в куче.Отсюда и другое поведение.Например, обратите внимание на размещение new в clone() для небольших объектов: это создает новый объект в указанной ячейке памяти, не выделяя его.Когда вы создаете объект с использованием размещения new, вы должны явно вызвать деструктор на нем перед освобождением памяти, и это то, что делает static_delete().

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

1 голос
/ 31 октября 2011

Я отредактировал ваш вопрос, добавив ссылку на исходный файл, поскольку, очевидно, большинство ответивших здесь не читали его, чтобы увидеть, что на самом деле происходит.Я признаю, что это, вероятно, один из самых запутанных фрагментов кода в FireBreath;в то время я пытался избежать использования boost, и это сработало очень хорошо.

С тех пор я рассмотрел вопрос о переключении на boost :: any (для тех, кто жаждет это предложить, нет, boost :: вариантуне сработает, и я не собираюсь объяснять, почему здесь; задайте еще один вопрос, если вы действительно заботитесь), но мы настроили этот класс на достаточное количество, чтобы сделать его именно тем, что нам нужно, и повысить его: любой будет трудно настроить ваналогичным образом.Больше всего мы следовали старому принципу: если он не сломан, не исправляйте его!

Прежде всего, вы должны знать, что несколько экспертов по C ++ изучили этот код;да, он использует некоторые методы, которые многие считают сомнительными, но они очень тщательно продуманы, и они последовательны и надежны на компиляторах, поддерживаемых FireBreath.Мы провели обширное тестирование с использованием valgrind, визуального детектора утечек, LeakFinder и Rational Purify и никогда не обнаруживали утечек в этом коде.Это более чем сбивает с толку;для меня удивительно, что люди, которые не понимают код, предполагают, что автор не знает C ++.В этом случае Кристофер Диггинс (который написал код, который вы цитировали, и исходный класс cdiggins :: any, из которого он взят), кажется, знает C ++ очень хорошо, о чем свидетельствует тот факт, что он смог написать этот код.Код используется внутренне и высоко оптимизирован - возможно, даже больше, чем нужно FireBreath.Тем не менее, это послужило нам хорошо.

Я постараюсь объяснить ответ на ваш вопрос, насколько я помню;имейте в виду, что у меня не так много времени, и это было какое-то время, так как я действительно углубился в это.Основная причина, по которой «маленькие» типы используют другой статический класс, заключается в том, что «маленькие» типы в значительной степени являются встроенными;int, char, long и т. д. Предполагается, что все, что больше, чем void *, является объектом некоторого рода.Это оптимизация, позволяющая повторно использовать память всякий раз, когда это возможно, а не удалять и перераспределять ее.

Если вы посмотрите на код бок о бок, это будет намного понятнее.Если вы посмотрите на удаление и клонирование, то увидите, что на «больших» объектах оно динамически распределяет память;он вызывает «удалить» в удалении и в клоне он использует обычный «новый».В «маленькой» версии он просто хранит память внутри и повторно использует ее;он никогда не «удаляет» память, он просто вызывает деструктор или конструктор правильного типа в памяти, которая у него есть внутри.Опять же, это просто сделано ради эффективности.При перемещении обоих типов он вызывает деструктор старого объекта и затем назначает данные нового объекта.

Сам объект сохраняется как пустота *, поскольку мы на самом деле не знаем, какого типа будет объект;чтобы вернуть объект обратно, вы должны указать тип.Это часть того, что позволяет контейнеру хранить абсолютно любой тип данных.Вот почему там так много вызовов reinterpret_cast - многие люди видят это и говорят: «О, нет! Автор должен быть невежественным!»Однако, когда у вас есть пустота *, которую вам нужно разыменовать, это именно тот оператор, который вы бы использовали.

В любом случае, все это говорит, что cdiggins фактически выпустил новую версию своего любого класса в этом году;Мне нужно взглянуть на это и, вероятно, попробую вытянуть его, чтобы заменить текущий.Хитрость заключается в том, что я настроил текущую (главным образом, для добавления оператора сравнения, чтобы его можно было поместить в контейнер STL, и для добавления convert_cast), поэтому мне нужно убедиться, что я достаточно хорошо понимаю новую версию, чтобы сделать это безопасно.

Надеюсь, это поможет;статья, из которой я ее получил, находится здесь: http://www.codeproject.com/KB/cpp/dynamic_typing.aspx

Обратите внимание, что статья была обновлена ​​и, по-видимому, больше невозможно получить старую с оригиналом.

EDIT

С тех пор как я написал это, мы подтвердили некоторые проблемы со старым вариантом, и он был обновлен и заменен на тот, который использует boost :: any. Спасибо Дугме за большую часть работы над этим. FireBreath 1.7 (текущая основная ветвь на момент написания этой статьи) содержит это исправление.

...