Правильный способ использовать перечисления с передачей по значению и возвращением в функции? - PullRequest
3 голосов
/ 05 февраля 2011

Я понимаю, что заголовок вопроса очень расплывчатый, поэтому основной текст:)

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

namespace my_namespace
{
    namespace fileType
    {
        enum fileType
        {
            SOURCE,
            HEADER,
            RESOURCE
        };
    }
}
using namespace my_namespace::fileType;

, который позволил мне определить функцию:

fileType someFunction( const std::string &someFile, const fileType someType )
{
    //...
    return fileType::HEADER;
}

И я мог определить и сравнить переменные следующим образом:

fileType type = fileType::SOURCE;

Что удивительно.Хотя были некоторые предостережения.Заголовки (без каких-либо директив using) требовали удвоения предполагаемого имени перечисления, чтобы компилятор знал, что вы используете тип, а не пространство имен:

my_namespace::fileType::fileType soeFunction( const std::string &someFile, const my_namespace::fileType::fileType someType );

Что выглядит глупо, трудно читается и болезненночтобы понять.Кроме того, на первом уровне MSVC жалуется на нестандартное расширение (из-за двойного значения fileType в примере).Странно, что GCC не жалуется на самые строгие настройки, но, эй, это другая история.

Теперь я хочу переписать мои enum таким образом, чтобы они (анонимно) были заключены в структуру вместопространство имен, допускающее единую квалификацию при объявлении функций, таким образом отключая предупреждение MSVC.Но как мне написать оператор return в этом случае.Обязательно ли предоставлять конструктор / оператор преобразования или есть способ обойти это, которого я не видел?

Пример:

// enum definition
namespace my_namespace
{
    struct fileType
    {
        enum
        {
            SOURCE,
            HEADER,
            RESOURCE
        };
    }
}
using my_namespace::fileType;

// function declaration in header
my_namespace::fileType someFunction( const std::string &s, const my_namespace::fileType type );

// function implementation in .cpp file
using my_namespace::fileType;

fileType someFunction( const string &s, const fileType type )
{
    //...(problem is situated below)
    return fileType::SOURCE;
}

Это иллюстрирует то, что я хотел бы сделать.Я хотел бы избежать явного вызова конструктора структуры enum: fileType(fileType::SOURCE), что оставило бы меня с двойным fileType использованием.

Спасибо за помощь!

PS: если этот вопросЯ уже извинялся, но я не нашел хорошей альтернативы ни в Google, ни в предыдущих вопросах SO на эту тему.

Ответы [ 3 ]

10 голосов
/ 05 февраля 2011

Лично я использую очень простой трюк:

struct EnumName
{
  enum type {
    MemberOne,
    MemberTwo,
    ...
  };
};

typedef EnumName::type EnumName_t;

// Usage
EnumName_t foo = EnumName::MemberOne;

В C ++ 0x вы можете напрямую использовать счетчики:

enum struct EnunName // note: struct and class equivalent here
{
  MemberOne,
  MemberTwo,
  ...
};

// Usage
EnumName foo = EnumName::MemberOne;

Что будет действительно здорово:)

Примечание: перечисление scoped также не подлежит интегральному продвижению, что действительно здорово

0 голосов
/ 06 февраля 2011

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

Что касается «последней» записи для проверки аргументов, возможно, я упустил вашу точку зрения. Например, если вы передаете перечисление в качестве аргумента функции, например

void f( enum fileType type )  

Он не обеспечивает большей «безопасности типов», чем fileType без «последнего» элемента.

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

enum E
{
    first,
    e1 = first,
    e2,
    e3,
    last
}  

for (int i = first; i < last; ++i)
    ...  

если это так, разве не достаточно искусственно хотеть, чтобы два разных перечисления имели один и тот же именованный «последний» элемент в списке? В чем преимущество наличия одного «последнего» в двух разных перечислениях?
Улучшает ли это удобочитаемость по сравнению, например, с имея E_last и F_last?
Или это помогает сохранить написание хотя бы одной строки кода?
Кроме того, для чего используется 'last' в перечислениях с определенными областями? Перечисления с областью видимости не преобразуются в int по умолчанию. (Но, может быть, я неправильно понял, что вы хотели сделать ...)

О "Вы не можете держать значения enum короткими ..." Боюсь, я не понял, что вы хотели сказать здесь. Было ли во мне использование «const unsigned short» в примере с константой?

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

namespace nms
{
    typedef const unsigned short G1_t
    namespace G1
    {
        G1_t  first = 1;
        G1_t  type1 = 1;
        G1_t  type2 = 2;
        G1_t  type3 = 3;
        G1_t  last  = 3;
    }

    typedef const long G2_t
    namespace G2
    {
        G1_t  first = -1;
        G1_t  obj1  = -1;
        G1_t  obj2  =  0;
        G1_t  obj3  =  1;
        G1_t  last  =  1;
    }
}

Что касается «namespace - namespace - enum», мне не нравится дополнительная косвенность без существенных преимуществ. Что касается «namespace - struct / class - enum», то оно более философское:

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

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

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

0 голосов
/ 05 февраля 2011

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

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

namespace my_namespace
{
    enum fileType
    {
        SOURCE,
        HEADER,
        RESOURCE
    };
}

подходит вам лучше. Довольно часто, хотя в этом случае программисты предпочитают ставить перед фактическими значениями перечисления имя перечисления, что приводит к

namespace my_namespace
{
    enum fileType
    {
        FILE_TYPE_SOURCE,
        FILE_TYPE_HEADER,
        FILE_TYPE_RESOURCE
    };
}

здесь вы можете даже переименовать само перечисление в

    enum FILE_TYPE
    { ...

Также перечисления лучше использовать для группы значений, которые сами по себе являются неполными и никогда не будут расширяться в будущем, например перечисление для рабочих дней.
Если вы считаете, что enum fileType, вероятно, будет вечно неполным (поскольку существуют сотни типов файлов), вы можете выбрать еще один подход - использовать группы констант. Использование константных групп имеет преимущество перед enum: если вы расширяете эту константную группу, вам нужно только перекомпилировать модули / файлы, которые используют новые добавленные значения, тогда как если вы добавляете значение в enum, вам может потребоваться перекомпилировать все источники, которые используют это перечисление.

Постоянная группа может выглядеть так:

namespace nms
{
    typedef const unsigned short fileType_t;
    namespace fileType
    {
        fileType_t SOURCE   = 1;
        fileType_t HEADRE   = 2;
        fileType_t RESOURCE = 3;
    }
}

Конечно, если вы предпочитаете, вы можете переместить оператор typedef в пространство имен fileType или полностью избавиться от него и использовать «const unsigned short» непосредственно везде.

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

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

using nms;

nms::fileType_t someFunction( const string &s, const nms::fileType_t type )
{
    return nms::fileType::SOURCE;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...