Используя универсальные методы? - PullRequest
5 голосов
/ 21 января 2010

Каковы преимущества и недостатки использования универсальных методов (во время компиляции, времени выполнения, производительности и памяти)?

Ответы [ 8 ]

8 голосов
/ 21 января 2010

Хорошо, Java шаблоны и C ++ шаблоны настолько отличаются, что я не уверен, что можно ответить на них в одном вопросе.

Java Generics

Это довольно много для синтаксического сахара. Они реализуются через спорное решение под названием Тип стирания . Все, что они на самом деле делают, - это не дают вам разыграть много, что делает их более безопасными в использовании. Производительность идентична созданию специализированных классов, за исключением случаев, когда вы используете то, что было бы необработанным типом данных (int, float, double, char, bool, short). В этих случаях типы значений должны быть связаны с соответствующими им ссылочными типами (Integer, Float, Double, Char, Bool, Short), что имеет некоторые издержки. Использование памяти идентично, поскольку JRE просто выполняет приведение в фоновом режиме (что по сути бесплатно).

У Java также есть несколько приятных типов ковариация и контравариантность , благодаря которым все выглядит намного чище, чем без их использования.

Шаблоны C ++

Они фактически генерируют разные классы на основе типа ввода. std::vector<int> - это совершенно другой класс, чем std::vector<float>. Отсутствует поддержка ковариации или контравариантности, но есть поддержка передачи нетипов в шаблоны, частичная специализация шаблонов. Они в основном позволяют вам делать все, что вы хотите.

Однако, поскольку шаблоны C ++ создают разные классы для каждого варианта параметров их шаблонов, размер скомпилированного исполняемого файла больше. Кроме того, время компиляции значительно увеличивается, так как весь код шаблона должен быть включен в каждую единицу компиляции, и должно генерироваться намного больше кода. Однако фактический объем занимаемой памяти обычно меньше, чем у альтернативы (освобождает лишнюю пустоту *), а производительность выше, поскольку компилятор может выполнять более агрессивные оптимизации с известным типом.

EDIT (спасибо Дэвиду Родригесу): хотя универсальный класс Java компилирует сам себя, при использовании шаблона C ++ вы компилируете только то, что используете. Итак, если вы создаете std::vector<int> и используете только push_back и size, только эти функции будут скомпилированы в объектный файл. Это облегчает размер исполняемой проблемы.


Если вам интересно узнать о различиях между ними, посмотрите это сравнение обобщений в C #, Java и C ++.

2 голосов
/ 21 января 2010

Давайте просто забудем о преимуществах во время выполнения, так как это будет преждевременной оптимизацией.

Хотя во время компиляции универсальный метод может значительно улучшить читаемость, и в качестве бонуса вы обнаружите много ошибок гораздо раньше (во время компиляции, а не во время выполнения). Конечно, обязательным условием всего этого является то, что вы должны определить универсальный как можно более правильно, не слишком свободно и не слишком тесно.

2 голосов
/ 21 января 2010

В Java (не уверен в C ++) дженерики являются функцией времени компиляции. Они избегают использования потенциально небезопасных бросков. Например, типы, содержащиеся в коллекции, явно предоставляются компилятору, чтобы он знал, какие типы объектов / примитивов могут быть в него помещены. Это устраняет небезопасные предположения разработчиков о том, что может быть в коллекции в какое-то время. Это также служит для улучшения читабельности кода. В Java я не верю, что можно добиться увеличения производительности или памяти.

1 голос
/ 21 января 2010

Во время кодирования преимущество заключается в том, что вам не нужно приводить ваши объекты к определенному типу, таким образом, существует некоторая безопасность во время компиляции. Во время выполнения нет никакой разницы (в Java).

0 голосов
/ 27 января 2011

Вопреки убеждениям большинства разработчиков Java, вы можете избежать стирания типов, вы можете иметь усовершенствованные типы с помощью Generics.

Это правда, что это хитрость, но это можно сделать, если вам действительно нужно что-то ближе к шаблонам C ++.

http://www.jquantlib.org/index.php/Using_TypeTokens_to_retrieve_generic_parameters

Надеюсь, это поможет.

0 голосов
/ 21 января 2010

Я кодировал некоторые общие поля и классы записей. Они не используют шаблоны. Отличный атрибут в том, что у них есть метод read и write, который использует шаблон проектирования Visitor .

Одним приятным атрибутом является то, что вы можете обрабатывать записи, не зная деталей. (BWT, запись состоит из одного или нескольких полей). Таким образом, запись будет считываться путем передачи Reader (класс Visitor для чтения полей) в каждое поле и использование поля для использования данного читателя для заполнения его членов. Аналогично с письмом.

Если мне нужно прочитать из XML-файла или базы данных, я просто создаю Reader , который специализируется на чтении из XML или базы данных. Это не требует никаких изменений в классах Record или Field. Красиво, быстро и просто.

Один недостаток в том, что я не могу легко увидеть запись в отладчике. Мне нужно написать код для печати записи или использовать оператор if для перехвата при обнаружении определенной записи.

Я поражен тем, сколько работы можно выполнить, не зная деталей объектов и придерживаясь интерфейсов.

0 голосов
/ 21 января 2010

Вы также просили недостатки, вот один.

Универсальное программирование на C ++ может привести к некоторому довольно «космическому» коду, который может быть очень многословным и трудным для чтения и понимания людьми. То есть люди, отличные от того, кто его создал. Как таковой, он может быть сложным в обслуживании и использовании. Код, который трудно поддерживать или использовать, имеет большой удар по нему. Одно из мест, где я обнаружил, что это особенно верно, - использование классов политики .

Вот пример. Некоторое время назад я написал менеджер ресурсов на основе политик. Вроде как умный указатель, но достаточно универсальный, чтобы его можно было использовать для любого ресурса, а не только для памяти. Такие вещи, как мьютексы, ресурсы GDI (Windows) и т. Д. Мотивация написания этого была двоякой. Во-первых, я просто хотел написать это :), а во-вторых, я хотел создать хранилище кода, который мог бы быть полезен для управления ресурсами всех видов. Для того, чтобы он был в целом полезен, люди должны захотеть его использовать.

Итак, позвольте мне спросить вас, хотите ли вы использовать это?

    /*** COPY POLICIES ***/
    class SimpleCopyPolicy
    {
    public:
        template<class Resource> Resource copy(const Resource& rhs) const { Resource ret = rhs; return ret; }
    protected:
        ~SimpleCopyPolicy(){};
    };

    class DuplicateHandleCopyPolicy
    {
        public:
            HANDLE sourceProcess, targetProcess;
            DWORD access, options;
            BOOL inherit;

            DuplicateHandleCopyPolicy(HANDLE sourceProcess_=GetCurrentProcess(), HANDLE targetProcess_=GetCurrentProcess(), DWORD access_=0, BOOL inherit_=FALSE,DWORD options_=DUPLICATE_SAME_ACCESS)
            : sourceProcess(sourceProcess_), targetProcess(targetProcess_), access(access_), inherit(inherit_), options(options_) {}

            template<class Resource> Resource copy(const Resource & rhs) const
            {
                Resource ret;
#               if defined(VERBOSE_STLEXT_DEBUG) & defined(MHDAPI)
                if( !verify( DuplicateHandle(sourceProcess, rhs, targetProcess, &ret, access, inherit, options) ))
                {
                    DWORD err = GetLastError();
                    mhd::WarningMessage("DuplicateHandleCopyPolicy::copy()", "Error %d Copying Handle %X : '%s'",
                    err, rhs, stdextras::strprintwinerr(err).c_str() );
                }
                else
                    mhd::OutputMessage("Duplicated %X to %X", rhs, ret);
#               else
                DuplicateHandle(sourceProcess, rhs, targetProcess, &ret, access, inherit, options);
#               endif
                return ret;
            }

        protected:
            ~DuplicateHandleCopyPolicy(){};
    };

    /*** RELEASE POLICIES ***/
    class KernelReleasePolicy
    {
    public:
        template<class Handle> bool release(Handle& h)
        {
#           if defined(VERBOSE_STLEXT_DEBUG) & defined(MHDAPI)
            OutputMessage("Closing %X", h);
#           endif
            return 0 != CloseHandle(h);
        }
    };
    class CritsecReleasePolicy
    {
    public:
        template<class Handle> bool release(Handle& h)
        {
            DeleteCriticalSection(&h);
            return true;
        }
    protected:
        ~CritsecReleasePolicy() {};
    };
    class GDIReleasePolicy
    {
    public:
        template<class Handle> bool release(Handle h) { return 0 != DeleteObject(h); }
    protected:
        ~GDIReleasePolicy(){};
    };
    class LibraryReleasePolicy
    {
    public:
        template<class Handle> bool release(Handle h) { return 0 != FreeLibrary(h); }
    protected:
        ~LibraryReleasePolicy(){};
    };
#   ifdef WINSOCK_VERSION
    class SocketReleasePolicy
    {
    public:
        template<class Handle> bool release(Handle h) { return 0 != closesocket(h); }
    protected:
        ~SocketReleasePolicy(){};
    };
#   endif

    class DestroyWindowPolicy
    {
    public:
        template<class Handle> bool release(Handle h) { return 0 != DestroyWindow(h); }
    protected:
        ~DestroyWindowPolicy() {};
    };

    /*** LOCKING POLICIES ***/
    class WFSOPolicy    // Wait For Single Object
    {
    public:
        WFSOPolicy(DWORD timeout_=INFINITE) : timeout(timeout_) {};

        template<class Handle> bool wait(Handle& h) const
        {
#           if defined(VERBOSE_STLEXT_DEBUG) & defined(MHDAPI)
            DWORD ret = ::WaitForSingleObject(h,timeout);
            if( !verify( WAIT_OBJECT_0 == ret ))
            {
                DWORD err = GetLastError();
#               ifdef UNICODE
                mhd::WarningMessage("WFSOPolicy", "Error %d Waiting for object %X [Timeout %s] : '%S'",
                    err, h, INFINITE==timeout?"INFINITE":std::formatstr("%d ms", timeout).c_str(),
                    stdextras::strprintwinerr(err).c_str() );
#               else
                mhd::WarningMessage("WFSOPolicy", "Error %d Waiting for object %X [Timeout %s] : '%s'",
                    err, h, INFINITE==timeout?"INFINITE":std::formatstr("%d ms", timeout).c_str(),
                    stdextras::strprintwinerr(err).c_str() );
#               endif
                return false;
            }
            return true;
#           else
            return WAIT_OBJECT_0 == ::WaitForSingleObject(h,timeout);
#           endif
        }

        DWORD timeout;
    };


    /*** LOCK/UNLOCK POLICIES ***/
    class CritsecLockPolicy  // CRITICAL_SECTION lock/unlock policies
    {
    public:
        template<class Handle> bool lock(Handle& h)
        {
            EnterCriticalSection(const_cast<CRITICAL_SECTION*>(&h));
            return true;
        }
        template<class Handle> bool unlock(Handle& h) 
        {
            LeaveCriticalSection(&h);
            return true;
        }
    };

    template<DWORD waitTimeout = INFINITE>
    class MutexLockPolicy : public WFSOPolicy
    {
    public:
        MutexLockPolicy() : WFSOPolicy(waitTimeout) {};
        template<class Handle> bool lock(Handle& h) const
        {
            return wait(h);
        }
        template<class Handle> bool unlock(Handle& h) const 
        {
            return 0 != ReleaseMutex(h);
        }
    };

    class PlaceboLockPolicy // this lock policy doesnt actually do anything!  useful for debugging & experimentation
    {
    public:
        PlaceboLockPolicy() {};
        template<class Handle> bool lock(Handle&) const
        {
            return true;
        }
        template<class Handle> bool unlock(Handle&) const
        {
            return true;
        }
    };


    template<class Resource, typename ReleasePolicy, typename CopyPolicy = SimpleCopyPolicy>
    class simple_auto_resource : public ReleasePolicy, public CopyPolicy
    {
    public:
        typedef simple_auto_resource<Resource,ReleasePolicy,CopyPolicy> base_type;

        simple_auto_resource() : res(0) {}
        simple_auto_resource(const Resource & r) : res(copy(r)) {}
        ~simple_auto_resource() { if(res) release(res); }

        void clear() { if(res) release(res); res = 0; }

        Resource& get() { return res; }
        const Resource& get() const { return res; }

        Resource detach() { Resource ret = res; res = 0; return ret; }

        operator const Resource&() const { return get(); }
        operator Resource&() { return get(); }

        base_type& operator=(const Resource& rhs) { clear(); res = copy(rhs); return * this; }

        template<class Comp> bool operator==(const Comp& rhs) const { return res == (Resource)rhs; }
        template<class Comp> bool operator!=(const Comp& rhs) const { return res != (Resource)rhs; }
        template<class Comp> bool operator<(const Comp& rhs) const { return res < (Resource)rhs; }
    private:
        Resource res;
    };

    typedef simple_auto_resource<HBRUSH,GDIReleasePolicy> auto_brush;
    typedef simple_auto_resource<HINSTANCE, LibraryReleasePolicy> auto_lib;
    typedef simple_auto_resource<CRITICAL_SECTION, CritsecReleasePolicy> auto_critsec;
    typedef simple_auto_resource<HWND,DestroyWindowPolicy> auto_destroy_hwnd;
    typedef simple_auto_resource<HANDLE,KernelReleasePolicy,DuplicateHandleCopyPolicy> auto_kernelobj;  
#   ifdef WINSOCK_VERSION
    typedef simple_auto_resource<SOCKET,SocketReleasePolicy> auto_socket;
#   endif

    typedef auto_kernelobj auto_mutex;
    typedef auto_kernelobj auto_event;
    typedef auto_kernelobj auto_filehandle;
    typedef simple_auto_resource<HANDLE,KernelReleasePolicy> auto_localkernelobj;   
    typedef simple_auto_resource<HANDLE,KernelReleasePolicy> auto_localmutex;   
    typedef simple_auto_resource<HANDLE,KernelReleasePolicy> auto_localevent;   
    typedef simple_auto_resource<HANDLE,KernelReleasePolicy> auto_thread;   
    typedef simple_auto_resource<HMODULE,KernelReleasePolicy> auto_hmodule;

Если ваш ответ «нет, слишком сложный», это как раз моя точка зрения.

0 голосов
/ 21 января 2010

Основным преимуществом обоих языков является безопасность типов: например, компилятор гарантирует, что ваш List<Foo> list будет содержать только Foo объектов. Без обобщений (например, в более ранних версиях Java) ваш список будет принимать любой класс, который наследует Object.

Производительность Java отличается от C ++. В C ++ универсальный класс специализируется во время процесса компиляции, поэтому нет никаких накладных расходов вообще.

В Java, с другой стороны, дженерики были реализованы поверх существующей спецификации JVM, поэтому компилятор создает байт-код Java, который фактически использует приведение типов, которые не являются бесплатными. С другой стороны, альтернативой дженерикам является обработка всего как Object, что также требует приведения типов, поэтому на самом деле нет разницы в производительности между этими двумя альтернативами в Java.

Наконец, в некотором роде, когда дженерики были представлены в Java, они также добавили автобокс, который позволяет вам использовать примитивные типы с дженериками. Автобокс означает, что примитивный тип автоматически упаковывается в эквивалент класса, то есть int помещается в объект Integer, когда он используется в общем контексте. Это снова добавляет издержки времени выполнения, поскольку сначала необходимо создать новый экземпляр Integer, а затем собрать мусор.

...