Pull to refresh

Comments 26

По моему опыту (меня тоже время от времени пробивает на подобные макросы) — это удобно до тех пор, пока с проектом идёт активная работа. Стоит отложить проект на несколько месяцев — и некоторые макросы потом начинают доставлять неудобства — приходится постоянно заглядывать в определения. Всё хорошо в меру.
От подобных проблем спасает волшебная комбинация клавиш в IDE, которая раскрывает весь препроцессор в выделенном коде.

Дойдут руки, думаю написать, как это сделать для Visual Studio. По сути для этого нужно написать небольшой макрос на VBA, который вытаскивает из нужного проекта настройки компиляции (дефайнов и инклудов) и вызывает специальную утилиту, которая, в свою очередь, вызывает компилятор с опцией «preprocess to file». Затем из полученного файла вытаскивается нужный кусок и отображается пользователю.

Например. Имеем такой код:
DEFINE_ENUM_WITH_DEFAULT_VALUE( EHumanizationMode, eCppCode,
    ( ( eDisabled ) )
    ( ( eCppCode ) )
    ( ( eAutoexpCode ) ) );

Выделяем, нажимем кнопочку, привязанную к VBA макросу, немножко ждем и видем на экране:
//
// CodeFactory: BEGIN Auto generated preprocessed code
//
class EHumanizationMode : public ISmartEnumBase
{
    private: template< class T > operator T() const;
    template< class T >
    operator T();
    public: enum EImpl
    {
        eDisabled, eCppCode, eAutoexpCode
    };
    private: EImpl m_eValue;
    public: inline EHumanizationMode( EImpl m_eValue ) : m_eValue( m_eValue )
    {
    }
    public: inline bool operator==( const EHumanizationMode &rEnum ) const
    {
        return m_eValue == rEnum.m_eValue;
    }
    inline bool operator==( const EImpl oValue ) const
    {
        return m_eValue == oValue;
    }
    template< class T >
    inline bool operator!=( const T &rValue ) const
    {
        return !( *this == rValue );
    }
    public: inline operator EImpl() const
    {
        return m_eValue;
    }
    inline operator EImpl&()
    {
        return m_eValue;
    }
    public: inline int ToInt() const
    {
        return reinterpret_cast< const int & >( m_eValue );
    }
    inline int &ToInt()
    {
        return reinterpret_cast< int & >( m_eValue );
    }
    const char *ToString() const
    {
        switch( m_eValue )
        {
            case EHumanizationMode::eDisabled : return "EHumanizationMode::eDisabled";
            case EHumanizationMode::eCppCode : return "EHumanizationMode::eCppCode";
            case EHumanizationMode::eAutoexpCode : return "EHumanizationMode::eAutoexpCode";
            default: ((void)0);
            abort();
            return "Error";
        };
    }
    static EHumanizationMode FromInt( int eValue )
    {
        ((void)0);
        EHumanizationMode oRes;
        oRes.ToInt() = eValue;
        return oRes;
    }
    static bool ValidateValue( const int eValue )
    {
        switch( eValue )
        {
            case EHumanizationMode::eDisabled : case EHumanizationMode::eCppCode : case EHumanizationMode::eAutoexpCode : return true;
            default: return false;
        };
    }
    public: inline EHumanizationMode() : m_eValue( eCppCode )
    {
    }
};;
//
// CodeFactory: END Auto generated preprocessed code
//

Сейчас есть небольшие проблемы с форматированием результата, но они решаемые…
Удобно — не то слово, ещё и безопасно. Описанная в начале ситуация с забыванием добавить работу с полем в одном из мест — это не для красного словца, а несколько раз со мной случалась. Так что речь не столько об удобстве, сколько о желании избежать таких проблем в дальнейшем.
Я делаю генерацию подобного кода отдельным скриптом.
Во-первых, получается нормальный синтаксис в основном коде, что все-таки удобнее чем такие вот макросы.
Во-вторых, возможности скриптовых языков намного больше чем у макросов С.

Но есть и недостатки (для меня несущественные) — специальная организация проекта, чтобы сгенерированный код не путался в контроле версий, а создавался в момент сборки.
Вот это
#define END_MODEL_TYPE					\
};	void TModelTypeReflection::RegisterTypes()	{

безопасно?

Ну пусть в вашем случае проблемы не будет. А если у нас есть, например,
#define START_ME <some code here>
#define END_ME }; someOtherCode {
struct MyStruct
{
START_ME
<...>
END_ME
}

На первый взгляд все нормально. Потом человек, который не знает, что это за макрос (ну забыл он посмотреть, или просто не там поглядел, или еще что-нибудь) дописывает еще одно поле:
struct MyStruct
{
START_ME
<...>
END_ME
int iWasAddedLater;
}

На вид — все здорово, какой-то макрос начал какой-то блок, потом закончил его. Все прекрасно. А на деле — отнюдь. На мой взгляд (на объективность не претендую), такая ситуация ничуть не менее вероятно чем «забыл инициализировать новое поле».
Думаю ничего страшного не случится. Сначала компилятор поругается ворнингом на неиспользуемую переменную, потом сильно поругается на код, который к этому полю обращается (не просто же так это поле было добавлено), ибо такого поля не окажется.
Поддерживаю автора. Подобные вещи иногда значительно облегчают жизнь С++ программиста.
При переходе на C# остро страдаю от отсутствия подобных инструментов! приходится осваивать кодогенерацию…
Альтернативное решение, позволяющее объединить объявление с реализацией — использование кодогенератора, но и этот подход тоже имеет свои недостатки.


Мне как раз кодогенерация, при всех её недостатках, кажется более удобным решением. Особенно перспективным кажется кодогенерация макросов и последующее их использование.

Почему фундаментальные принципы (избегать дублирования) нарушаются в самом синтаксисе языка?
<ирония>Видимо, потому что язык появился раньше чем фундаментальные принципы и потому является более фундаментальным.

*интересно что будет, если забыть закрыть тег иронии??
Возникнет антиирония и аннигиляция :)
последнее можно сделать через TypeList и шаблон с рекурсивным вызовом функции регистрации
Конечно можно, но целью было оставить одно единственное место, куда нужно добавлять новый элемент перечисления, в приведённом коде их по-прежнему два (одно в enum, второе в typelist).
да, я понял.
если элементами тайплиста будут классы, то их идентификаторы можно получать из тайплиста, что оставит только одно объявление(я недавно в своем блоге о подобном писал pushkoff.blogspot.com/2012/04/blog-post.html). с интами этот номер не прошел.
По заголовку подумалось, что статья об объединении .h и .cpp.
Цель одна — не дублировать код, но техники — совсем разные.
Предпочитаю избегать переопределения конструкторов копирования и операторов присвоения в пользу использования всякого рода врапперов и умных указателей.

Я заметил, что как правило оператор присвоения переопределяется из-за одного-двух мемберов, а остальным, допустим десяти, отлично подходит и стандартный оператор присвоения. В таком случае можно завернуть эти два несчастных мембера в структуру с переопределенным оператором присвоения, а основной класс не трогать.
Многие люди считают, что умные указатели привносятт много накладных расходов, в следствии чего отказываются от их использования…
Многие люди не проверяют свои догадки измерениями и занимаются преждевременной оптимизацией и при этом неоправданно усложняют код программ.
Sign up to leave a comment.

Articles