Как использовать C # -подобные атрибуты в C ++ - PullRequest
11 голосов
/ 11 июля 2009

Я рассматриваю возможность использования C ++ для личного проекта. Я хотел бы сделать его независимым от платформы (пожалуйста, не Mono, поскольку некоторые платформы еще не поддерживают его), и именно поэтому я рассмотрел C ++.

Однако у меня есть одно сомнение. Я полюбил атрибуты C # и хотел бы знать, могу ли я использовать что-то подобное в C ++.

Кроме того, возможно ли использовать для этого шаблон декоратора?

РЕДАКТИРОВАТЬ: Я бы сейчас рассмотрел другие возможности или приближения для этого вопроса, т.е. некоторый способ добавить дополнительное поведение к классу во время выполнения.

РЕДАКТИРОВАТЬ 2: Java не вариант, потому что некоторые устройства я хотел бы перенести его, чтобы не поддерживать Java.

Ответы [ 9 ]

9 голосов
/ 26 июля 2009

Чтобы "прикрепить дополнительное поведение к классу во время выполнения" в большинстве любых ОО-языков, я рекомендую шаблон проектирования Стратегия - удерживать класс (и / или его экземпляры) (через указатель в C ++) [ссылка на другие языки] - экземпляр подходящего интерфейса / абстрактного класса, известного как «интерфейс стратегии» (с одним методом [конечно, виртуальным в C ++, не финальным в языках, которые имеют final и т. д. - - IOW, переопределяемый метод! -)] для каждой точки расширяемости) и, как правило, предоставляют методы получения и установки (или свойства или все, что подходит для конкретного языка) для доступа и изменения этого экземпляра.

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

Я часто рекомендую этот подход "высокой церемонии" (для этой конкретной цели) даже в динамических языках, таких как Python или Ruby, которые также позволяют более неформальные подходы с помощью утки и возможность прямого доступа к объекту класса. внутренности другого - такие динамические способности, вообще говоря, весьма полезны, но для этой конкретной цели (как я думаю, «изменение поведения класса во время выполнения»), по моему опыту, более высоко спроектированный и контролируемый подход приводит к понятный и понятный код (этот бит противоречив: многие разработчики в сообществах с динамическими языками любят «подлатывать обезьяны» даже в таких ситуациях, но дюжина лет успешной практики разработки на динамических языках, в основном на Python, заставила меня склониться к другому).

В соответствующем случае вы можете модифицировать фундаментальный подход Strategy DP различными способами; например, когда изменяемая функциональность аккуратно разделяется на несколько сплоченных групп, лучше всего «разбить» объект «Стратегия» на несколько простых и сплоченных (DrawingStrategy, PersistenceStrategy, BusinessRulesStrategy и т. д.).

Весь этот подход не заменяет выполнение надлежащего анализа и, следовательно, правильного проектирования, поскольку он не позволит расширить функциональные возможности класса вдоль оси, которая изначально не учитывалась; скорее, подход предназначен как правильный путь к architect хорошо продуманному дизайну, обеспечивающему "крючки" для расширяемости хорошо контролируемым образом. Если в игру вступают новые соображения, все равно может потребоваться повторение и совершенствование дизайна классов, чтобы охватить такие новые идеи. Но затем, итеративная разработка (включая изменения и дополнения к оригинальному дизайну) неизбежна в любом богатом, сложном реальном проекте - Strategy DP - это всего лишь одна стрелка в вашем колчане, чтобы помочь сделать процесс более упорядоченным и эффективным.

5 голосов
/ 11 июля 2009

Для атрибутов класса, да. Просто определите базовый класс с именем Attributes для хранения информации об атрибутах и ​​наследуйте ее в любом классе, которому требуются атрибуты. Вы можете запросить его, используя RTTI cast.

Но для атрибутов метода или параметра это в принципе не вариант.

3 голосов
/ 28 июля 2009

Идея о том, как «смоделировать» атрибут класса с помощью специализации шаблона:

#include <string>
#include <iostream>

// attribute classes: the default implementation returns some
// default values for the attributes properties
template<typename TheClass> 
struct Attribute1{
    static const int Value = 0;
};

template<typename TheClass> 
struct Attribute2{
    static const std::string Value(){
        return "";
    }
};

Реализация атрибутов по умолчанию будет выбрана компилятором для класса без атрибутов:

// define a type without attributes
struct ClassWithoutAttributes{
};

Если мы хотим применить атрибуты к классу, мы используем специализацию шаблона:

// define a type with attributes; we "simulate" the attributes     
// template specialization
struct ClassWithAttributes{
};

// template-specialize Attribute1 for the class we want to apply the
// attribute to...
template<>
struct Attribute1<ClassWithAttributes>{
    static const int Value = 1;
};

// template-specialize Attribute2 
template<>
struct Attribute2<ClassWithAttributes>{
    static const std::string Value(){
        return "SomeString";
    }
};

Мы должны применять (шаблонно-специализированные) атрибуты для каждого класса, к которому мы хотим, чтобы они применялись:

class Item{
};

template<>
struct Attribute1<Item>{
    static const int Value = 2;
};

Пример:

// how to use the fake attributes:
void main(){
    // no template specialization for "ClassWithoutAttributes" => the compiler picks up the "default" values
    std::cout << "Attribute1 for type 'ClassWithoutAttributes' : " << Attribute1<ClassWithoutAttributes>::Value << std::endl;
    std::cout << "Attribute2 for type 'ClassWithoutAttributes' : " << Attribute2<ClassWithoutAttributes>::Value() << std::endl;
    // here the compiler picks up the attribute classes specialized for "ClassWithAttributes"
    std::cout << "Attribute1 for type 'ClassWithAttributes' : " << Attribute1<ClassWithAttributes>::Value << std::endl;
    std::cout << "Attribute2 for type 'ClassWithAttributes' : " << Attribute2<ClassWithAttributes>::Value() << std::endl;
}

Таким образом, атрибуты «применяются» к классу, а не к экземпляру, как при множественном наследовании; в любом случае принципиальное отличие состоит в том, что в этом случае атрибуты оцениваются во время компиляции, а не во время выполнения.

РЕДАКТИРОВАТЬ : изменил пример, чтобы показать, как применять несколько атрибутов к классу и как применять один и тот же атрибут к нескольким классам.

Отвечать на этот вопрос было весело для меня; на данный момент, во всяком случае, я чувствую, что лучший совет по этому вопросу заключается в том, что вы не должны пытаться программировать на c ++, как если бы это был c #. В любом случае, удачного кодирования!

2 голосов
/ 28 июля 2009

Создайте себе препроцессор, который превращает атрибут, подобный синтаксису, в реальные свойства и методы.

1 голос
/ 24 августа 2009

Я читал статью на CodeProject об этой проблеме: Реализация C ++ свойства и индексатора с модификаторами доступа . Я считаю, что это то, что вы хотите.

1 голос
/ 28 июля 2009

Вы спрашиваете об аспектно-ориентированном программировании? AspectC ++ в настоящее время является всего лишь исследовательским прототипом :( http://www.aspectc.org/

1 голос
/ 24 июля 2009

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

class whatever
{
public:
    static map<string, string> attribute_repository;
}

Боль, вероятно, не стоит пользы ...

1 голос
/ 11 июля 2009

C ++ 0x будет поддерживать ограниченные атрибуты. Но я сомневаюсь, что это поможет вам в вашем текущем проекте, или даже в следующих нескольких (это не будет здесь некоторое время).

Я отвечу, если вы ссылаетесь на свойства, которые я часто желал.

Вы можете поддерживать некоторые свойства. Общая реализация этого будет немного сложнее. Но для одного человека это может быть просто

class Foo
{
    class MyI_property
    {
    public:
        MyI_property( Foo* parent ) :
            m_parent(parent)
        { }

        // getter
        operator int( void )
        { return m_parent->get_i(); }

        // setter
        MyI_property& operator = ( int i )
        { m_parent->set_i(i); }

        // some other operators you might want to implement
        int* operator&( void );
        MyI_property& operator += ( int rhs );

    private:
        Foo* m_parent;
    };

public:
    Foo( void ) :
        MyI(this)
    { }

    MyI_property MyI;

private:
    int& get_i( void );
    void set_i( int i );
};

Foo f;

f.MyI = 10; // calls Foo::set_i
int i = f.MyI; // calls Foo::get_i
int j = 2 * f.MyI + f.MyI;

// could work with proper overloads in MyI_property
f.MyI += 20;
int& i = f.MyI;
int* i = &f.MyI;

Ради краткости я игнорировал правильность констант.

0 голосов
/ 26 июля 2009

В примере MVC, к которому вы обращаетесь, атрибуты метода используются через отражение. Поскольку в C ++ нет отражения, вы можете вместо этого использовать генерацию кода: какой-то язык определения интерфейса и генератор, создающий код на C ++. В этом сценарии вы можете добавить концепцию атрибутов к вашему языку интерфейса и заставить генератор переводить атрибуты соответствующим образом.

Отличный пример - буфер протокола Google : существует специальный формат .proto для определения сообщений, который, в частности, включает в себя «опции» (концепция, очень близкая к атрибутам .net ). Есть также компиляторы для разных языков программирования, включая C ++. Последний генерирует файлы .h / .cpp для сообщений, описанных в формате .proto. Сгенерированные классы, в частности, предоставляют функциональность отражения, которая позволяет программно проверять, установлены ли определенные опции для сервисов / методов / сообщений / и т. Д.

Очевидно, что подход к генерации кода нелегок для реализации, однако он может дать очень эффективные решения.

...