В C++, как я могу избежать #include заголовочного файла, когда мне нужно использовать перечисление?
В моих заголовочных файлах C++ я стараюсь использовать предварительные объявления (класс MyClass;) вместо #include заголовка класса, как рекомендовано во многих стандартах кодирования C++ (например, руководство по стилю Google C++).
К сожалению, когда я ввожу перечисления, я больше не могу делать предварительную декларацию. Как это:
//// myclass1.hpp ////
class MyClass1
{
enum MyEnum1
{
Enum_A, Enum_B, Enum_C
};
};
//// myclass2.hpp ////
// I want to avoid this
#include "myclass1.hpp"
// I'd prefer to do this (forward declaration)
class MyClass1;
class MyClass2
{
// This is o.k.: I only need to forward declare MyClass1
MyClass1* ptr;
// This forces me to #include, but I don't want to!
void func( MyClass1::MyEnum1 e );
};
Лучшее решение, которое я могу придумать, - это заменить перечисления константами-членами:
//// myclass1.hpp ////
MyClass1
{
static const int Enum_A;
static const int Enum_B;
static const int Enum_C;
};
//// myclass1.cpp ////
const int Enum_A = 1;
const int Enum_B = 2;
const int Enum_C = 3;
В этом случае, однако, решение кажется хуже, чем проблема.
В настоящее время я изучаю крупномасштабный программный дизайн C++ (Lakos) и эффективно работаю с устаревшим кодом (Feathers) на предмет методов разрушения зависимостей, но пока не нашел хорошего решения.
8 ответов
Вы не можете пересылать декларируемые значения enum - и ваш обходной путь - это шаг по пути к полному безумию.
Испытываете ли вы какие-либо серьезные замедления компиляции, вызванные заголовками? Если нет, просто включите их. Использование предварительных деклараций - это не "лучшая практика", это взлом.
Это сложно сделать красиво. Возможно движется enum
с общим заголовочным файлом будет разумным решением?
Редактировать: я знаю вопрос, заданный, чтобы избежать включения заголовочного файла, но просто нет способа (AFAIK) сделать это. Перемещение перечислений в отдельный заголовочный файл, по крайней мере, минимизирует количество содержимого в заголовочном файле, которое вам нужно включить. Это, конечно, лучше, чем сумасшествие, предложенное в вопросе!
Сильно типизированные перечисления C++0x могут быть объявлены заранее. GCC 4.4.0 и CodeGear C++Builder 2009 поддерживают строго типизированные перечисления.
Существует несколько классов, похожих на enum, таких как Boost.Enum (предложенный, но никогда не завершенный и не принятый), доступный для загрузки из Boost Vault по этой ссылке. Поскольку Boost.Enums являются классами, они могут быть объявлены вперед.
Однако простое размещение перечислений в отдельном файле (как в этом ответе) кажется самым простым и лучшим решением (за исключением поддержки C++0x).
Вы можете использовать предварительные объявления только тогда, когда вы объявляете указатель. Если вы объявляете переменную без указателя, вам нужно будет включить соответствующий заголовочный файл.
Поскольку переменная enum не является указателем, вы не можете использовать предварительные объявления. И я не думаю, что есть альтернативное решение.
Вы можете использовать аргументы шаблона для программирования против "общих" типов перечислений. Так же, как это:
// enum.h
struct MyClass1 { enum e { cE1, cE2, cELast }; };
// algo.h
// precondition: tEnum contains enumerate type e
template< typename tEnum > typename tEnum::e get_second() {
return static_cast<typename tEnum::e>(1);
}
// myclass1.h
// myclass.h
template< typename tClass1 >
class MyClass2
{
tClass1 * ptr;
void func( tClass1::e e );
};
// main.cpp
#include "enum.h"
#include "algo.h"
int main(){ return get_second<Enum>(); }
Я не думаю (я могу оказаться неверным), что вы можете объявить внутренний тип или перечисление. Вам понадобится определение класса включения, чтобы использовать перечисление.
Хотя большинство руководств по стилю обязывают не включать ненужные заголовки, в вашем случае заголовок необходим. Другие варианты, которые вы можете рассмотреть, если вы действительно хотите избежать включения, будут определять перечисление вне класса и включать заголовок, который определяет перечисление.
Предварительная декларация перечислений фактически была предложена комитетом по стандартам C++. Смотрите эту статью (pdf). Это, безусловно, будет хорошей особенностью!
Если вы действительно столкнулись с замедлением компиляции из-за включения заголовка, другой вариант - использовать int
вместо enum
, Это довольно непопулярный подход, поскольку он снижает безопасность типов. Если вы используете этот подход, то я бы также рекомендовал добавить код для программной проверки границ:
// in class1.h
class Class1 {
public:
enum Blah {
kFirstBlah, // this is always first
eOne = kFirstBlah,
...
kLastBlah // this is always last
};
};
// in checks.h
#include <stdexcept>
namespace check {
template <typename T, typename U>
U bounds(U lower, T value, U upper) {
U castValue = static_cast<U>(value);
if (castValue < lower || castValue >= upper) {
throw std::domain_error("check::bounds");
}
return castValue;
}
} // end check namespace
// in class2.h
class Class2 {
public:
void func(int blah);
};
// in class2.cpp
#include "class2.h"
#include "class1.h"
#include "checks.h"
void Class2::func(int blah) {
Class1::Blah blah_;
blah_ = check::bounds(Class1::kFirstBlah, blah, Class1::kLastBlah);
}
Это не самое красивое решение, но оно решает проблему зависимости заголовка, перемещая некоторые из типов безопасности, которые статическая компиляция дает вам в код выполнения. Я использовал подобные подходы в прошлом и обнаружил, что check
Используемое таким образом пространство имен может сделать полученный код почти таким же читабельным, как enum
на основе кода с очень небольшими усилиями.
Предостережение заключается в том, что вы должны приложить усилия для написания безопасного кода, который я рекомендую, независимо от того, применяете ли вы этот подход или нет;)