Битовые поля C / C ++ по сравнению с побитовыми операторами для выделения битов, что быстрее, лучше, более переносимо?

Как разработчики Java, разрабатывающие определенные API, мы часто сталкиваемся с этой проблемой. Я повторил свои собственные сомнения, когда столкнулся с этим сообщением, но у меня есть подробное обходное решение:

// class name is awful for this example, but it will make more sense if you
//  read further
public interface MetaDataKey<T extends Serializable> extends Serializable
{
    T getValue();
}

public final class TypeSafeKeys
{
    static enum StringKeys implements MetaDataKey<String>
    {
        A1("key1");

        private final String value;

        StringKeys(String value) { this.value = value; }

        @Override
        public String getValue() { return value; }
    }

    static enum IntegerKeys implements MetaDataKey<Integer>
    {
        A2(0);

        private final Integer value;

        IntegerKeys (Integer value) { this.value = value; }

        @Override
        public Integer getValue() { return value; }
    }

    public static final MetaDataKey<String> A1 = StringKeys.A1;
    public static final MetaDataKey<Integer> A2 = IntegerKeys.A2;
}

. В этот момент вы получаете преимущество от истинного постоянного значения enum (и все преимущества, которые идут с этим), а также уникальная реализация interface, но у вас есть глобальная доступность, желаемая enum.

Ясно, что это добавляет многословие, что создает потенциал для ошибок копирования / вставки. Вы можете сделать enum s public и просто добавить дополнительный слой к их доступу.

Конструкции, которые имеют тенденцию использовать эти функции, как правило, страдают от хрупких реализаций equals, потому что они обычно связаны с другое уникальное значение, такое как имя, которое может быть невольно дублировано в кодовой базе для аналогичной, но другой цели. Используя enum s по всей доске, равенство - это халява, которая невосприимчива к такому хрупкому поведению.

. Основным недостатком такого, как система, помимо многословия, является идея преобразования вперед и назад между глобально уникальные ключи (например, маршалинг и JSON). Если они являются просто ключами, то их можно безопасно восстановить (продублировать) за счет потери памяти, но используя то, что было ранее слабостью - equals - в качестве преимущества.

обходной путь к этому, который обеспечивает уникальность глобальной реализации, загромождая его анонимным типом для глобального экземпляра:

public abstract class BasicMetaDataKey<T extends Serializable>
     implements MetaDataKey<T>
{
    private final T value;

    public BasicMetaDataKey(T value)
    {
        this.value = value;
    }

    @Override
    public T getValue()
    {
        return value;
    }

    // @Override equals
    // @Override hashCode
}

public final class TypeSafeKeys
{
    public static final MetaDataKey<String> A1 =
        new BasicMetaDataKey<String>("value") {};
    public static final MetaDataKey<Integer> A2 =
        new BasicMetaDataKey<Integer>(0) {};
}

Обратите внимание, что каждый экземпляр использует анонимную реализацию, но ничего не нужно для его реализации, поэтому {} пустые. Это и запутывает, и раздражает, но работает, если ссылки на экземпляры предпочтительнее, а помехи сведены к минимуму, хотя это может быть немного загадочно для менее опытных разработчиков Java, что затрудняет его поддержку.

Наконец, единственный способ обеспечить глобальную уникальность и переназначение - быть немного более творческим в том, что происходит. Наиболее распространенное использование для общедоступных интерфейсов, которые я видел, для котей MetaData, которые имеют тенденцию смешивать множество разных значений с разными типами (T в каждом ключе):

public interface MetaDataKey<T extends Serializable> extends Serializable
{
    Class<T> getType();
    String getName();
}

public final class TypeSafeKeys
{
    public static enum StringKeys implements MetaDataKey<String>
    {
        A1;

        @Override
        public Class<String> getType() { return String.class; }

        @Override
        public String getName()
        {
            return getDeclaringClass().getName() + "." + name();
        }
    }

    public static enum IntegerKeys implements MetaDataKey<Integer>
    {
        A2;

        @Override
        public Class<Integer> getType() { return Integer.class; }

        @Override
        public String getName()
        {
            return getDeclaringClass().getName() + "." + name();
        }
    }

    public static final MetaDataKey<String> A1 = StringKeys.A1;
    public static final MetaDataKey<Integer> A2 = IntegerKeys.A2;
}

Это обеспечивает такую ​​же гибкость, как и первый вариант, и обеспечивает механизм получения ссылки посредством отражения, если это становится необходимым позже, поэтому избегая необходимости в последующем. Он также избегает многих ошибок, связанных с ошибкой копирования / вставки, которые первый параметр предоставляет, поскольку он не будет компилироваться, если первый метод неверен, а второй метод не нужно изменять. Единственное замечание заключается в том, что вы должны убедиться, что enum s предназначены для использования таким образом public, чтобы избежать доступа к ошибкам доступа, поскольку они не имеют доступа к внутреннему enum; если вы не хотите, чтобы эти MetaDataKey проходили через маршалированный провод, то их можно было скрывать из внешних пакетов, чтобы их автоматически отменить (во время маршалинга, рефлексивно проверить, доступен ли enum, и если это не так, то игнорируйте ключ / значение). Нет ничего выигранного или потерянного, сделав его public, за исключением предоставления двух способов доступа к экземпляру, если поддерживаются более очевидные static ссылки (поскольку экземпляры enum просто так или иначе).

Я просто хочу, чтобы они сделали это так, чтобы enum s могли расширять объекты на Java. Может быть, в Java 9?

Окончательный вариант действительно не решает вашу потребность, поскольку вы просите значения, но я подозреваю, что это приближается к фактической цели.

13
задан tanascius 17 March 2011 в 09:50
поделиться

7 ответов

Я бы предпочел использовать второй пример в предпочтении максимальной переносимости. Как указал Neil Butterworth, используя билетные поля только для родного процессора. ОК, подумайте об этом, что произойдет, если завтра произойдет Intel X86, кода будет застрять, что означает, что необходимо повторно реализовать битовые поля для другого процессора, скажем, чип RISC.

Вы должны посмотреть на большую картину и спросить, как OpenBSD удается портировать свои системы BSD для многих платформ, используя одну кодовую базу? Хорошо, я признаю, что это немного по вершине, и спонтируемо и субъективно, но реально говоря, если вы хотите портировать код на другую платформу, это способ сделать это, используя второй пример, который вы использовали в вашем вопросе Отказ

Не в одиночку, которые компиляторы для разных платформ будут иметь свой собственный путь прокладки, выравнивая биты для процессора, на котором включен компилятор. И, кроме того, как насчет энамика процессора?

никогда не полагаются на битфилд, как волшебная пуля. Если вы хотите скорость для процессора и будет исправлено на нем, то есть. Нет намерения портировать, то не стесняйтесь использовать биты. Вы не можете иметь оба!

7
ответ дан 1 December 2019 в 19:23
поделиться

Bitfields делают код гораздо более четкой, если они используются соответствующим образом. Я бы использовал битовые поля только в качестве космического устройства только. Одно обычное место, которое я видел, используемый, находится в компиляторах: часто тип или информация о символах состоит из куча истинных / ложных флагов. Bitfields идеально подходят здесь, поскольку типичная программа будет иметь много тысяч этих узлов, созданных, когда она скомпилирована.

Я бы не использовал битовые поля, чтобы сделать общее задание встроенного программирования: чтение и запись устройства устройств. Я предпочитаю использовать сдвиги и маски здесь, потому что вы получаете именно биты, документация говорит вам, что вам нужно, и вам не нужно беспокоиться о различиях в различных компиляторах реализации битов.

Что касается скорости, хороший компилятор даст тот же код для битовых полевок, которые замаскируют.

12
ответ дан 1 December 2019 в 19:23
поделиться

Первый -

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

Так что используйте первый.

5
ответ дан 1 December 2019 в 19:23
поделиться

Если вы хотите переносимость, избегайте битовых полей. И если вы заинтересованы в выполнении конкретного кода, нет альтернативы писать свои собственные тесты. Помните, Bitfields будут использовать процессор побитовые инструкции под капотом.

4
ответ дан 1 December 2019 в 19:23
поделиться

Не читайте многое другое в «не портативных битовых полях». Есть два аспекты битовых полей, которые являются определенными реализацией: подпись и макет и один неопределенный: выравнивание блока распределения, в котором они упаковываются. Если вам не нужно ничего, что эффект упаковки, используя их, так как портативный (при условии, что вы явно указываете , подписанный ключевое слово , где это необходимо) в качестве необходимых вызовов функций, которые также имеют неопределенные свойства.

Что касается производительности, профиль является лучшим ответом, который вы можете получить. В идеальном мире не было бы разницы между двумя писаниями. На практике могут быть некоторые, но я могу думать о целых причинах в одном направлении, что и другое. И это может быть очень чувствительно к контексту (логически бессмысленная разница между неподписанными и подписанными, например), поэтому измерять в контексте ...

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

1
ответ дан 1 December 2019 в 19:23
поделиться

Я думаю, что программист C будет иметь тенденцию к второму варианту использования битовых масок и логических операций для вывода значения каждого бита. Вместо того, чтобы иметь код, замученный шестнадцатеричными значениями, перечисления будут настроены, или, как правило, когда участвуют более сложные операции, макросы получают / устанавливают определенные биты. Я слышал на винограде, что структурные реализованные битовые поля медленнее.

1
ответ дан 1 December 2019 в 19:23
поделиться

Битфилды C были мертворожденными с момента их изобретения - по неизвестной причине. Людям они не нравились и вместо них использовались битовые операторы. Нужно ожидать, что другие разработчики не поймут код битфилда на Си.

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

6
ответ дан 1 December 2019 в 19:23
поделиться