2009-11-06 3 views
1

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

#define CONTAINS(Class, Name)\ 
    private:\ 
     std::list<Class> m_##Name##s;\ 
    public:\ 
     void add_##Name(const Class& a_##Name) {\ 
      m_##Name##s.push_back(a_##Name);\ 
     }\ 
     int get_##Name(int pos) {\ 
      return m_##Name##s.at(pos);\ 
     }\ 
     // ... more member functions 

Позже вы можете объявить класс как

class my_class { 
    CONTAINS(int, integer) 
    // ... 
}; 

и написать

my_class a(...); 
a.add_integer(10); 

I был озадачен этим пастом в макро-стиле, потому что мне не хватает конкретных контр-аргументов. Но кроме того, что я принимаю следующие плюсы

  1. вы можете легко добавить интерфейс списка для произвольных типов в классе
  2. вы избежите часто повторяющегося кода
  3. у вас есть простой в использовании интерфейс (как add_integer(10))

Теперь я ищу альтернативы, которые соответствуют всем этим пунктам выше и избегают этого старого стиля макросов C. Моя первая мысль была создать абстрактный шаблон базового класса

template<typename T> 
class list_interface { 
private: 
    std::list<T> m_list; 
public: 
    void add_element(const T& x) { 
     m_list.push_back(x); 
    } 
    // ... more member functions 
}; 

и добавить его в класс через наследование как этот

class my_class : public list_interface<int> { 
    // ... 
}; 

Теперь я могу писать слишком

my_class a; 
a.add_element(10); 

, но я м касается следующих пунктов:

  1. Вы можете добавить только один список вашего класса
  2. вы публично наследуетесь от класса без виртуального деструктора
  3. я не соответствую третьей точке профи (add_element(10) вместо add_integer(10))

Моих вопросов являются:

  1. Каковы недостатки старого C макро конструкции
  2. Как я могу обеспечить аналогичные функциональные возможности без макросов
+0

Вам нужен виртуальный деструктор, только если вы вызываете разрушение от базового класса (обычно через 'Base * р ; delete p; '). Вы можете запретить это, создав Constructor, Copy Constructor, Assignment Operator и Destructor 'protected'. Таким образом, очевидно, что базовый класс не предназначен для родословности ... но, конечно, это противоречит философии «наследования» публичного наследования ... –

ответ

2

Как насчет:

#include <vector> 

template<typename T> 
class Plop 
{ 
    std::vector<T> data; 
    public: 
     void add(T const& v) {data.push_back(v);} 
     T  get(int pos) {return data.at(pos);} // at() is not valid on lists. 
}; 

class my_class 
{ 
    public: 
     Plop<int>  integer; 
     Plop<float>  floater; 
}; 

int main() 
{ 
    my_class x; 
    x.integer.add(5);  // similar to x.add_integer(5); 
    x.integer.get(0);  // similar to x.get_integer(0); 
} 

Он отвечает всем требованиям:

  1. вы можете легко добавить интерфейс списка для произвольных типов в ваш класс
  2. Вы избегаете часто повторяющихся Код
  3. у вас есть простой в использовании интерфейс (например, add_integer (10))

Мои вопросы:

  1. Каковы недостатки старого C макро конструкции

    • Фактор ick.
    • Отладка.
    • Может передавать индивидуальный список другим функциям или методам.
  2. Как я могу предоставить аналогичную функциональность без макросов

    • См выше.
+0

+1. Это решение, по-видимому, наилучшим образом отвечает требованиям и позволяет избежать наследования. – phlipsy

1

мое мнение

1) Тьфу Тьфу. Комплексный макрос, который будет препятствовать отладке. Просто взгляд на макрос заставляет мою кожу сканировать.

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

+0

Моя первая реакция была примерно такая же, но это не серьезный недостаток этого решения. Отладка - хороший момент! – phlipsy

+0

Это вопиющее злоупотребление наследством. –

0

Для нескольких списков вы можете попробовать несколько наследований с помощью typedefs.Что-то вроде:

class my_class : public list_interface<int>, public list_interface<float> { 
public: 
    typedef list_interface<int> Ints; 
    typedef list_interface<float> Floats; 
//... 
}; 

и использовать как:

my_class a; 
a.Ints::add_element(10); 
a.Floats::add_element(10.0f); 
+0

Это отличная идея, чтобы справиться с первой точкой плюсов. Я еще не подумал об этом. К сожалению, он не очень хорошо справляется с третьим пунктом плюсов. – phlipsy

+0

Это потому, что ему не хватает тегов :) –

+0

Очень плохая идея, даже игнорируя тот факт, что невозможно иметь два списка одного и того же типа. Это нарушает принцип замещения Лискова - выводить B из A только тогда, когда используется A в любом месте B. –

1

Существует способ, в мета-программирования моды, и с помощью тегов.

Прежде всего, давайте рассмотрим решение roll your own.

Идея заключается в том, чтобы придумать с этим интерфейсом:

class my_class : public vector<Name, std::string>, public vector<Foo, int> 
{ 
}; 

И затем, чтобы использовать его как это:

my_class a; 
a.add<Name>("Peter"); 
a.add<Foo>(3); 

Теперь давайте нырять за обложках. Мы собираемся использовать SFINAE в сочетании с enable_if.

template <class Tag, class Type> 
class vector 
{ 
    template <class T, Return> 
    struct Enable 
    { 
    typedef typename boost::enable_if< 
         boost::is_same<T,Tag>, 
         Return 
        >::type type; 
    }; // Enable 
public: 
    template <class T> 
    typename Enable<T,void>::type 
    add(Type const& i) { m_elements.push_back(i); } 

    template <class T> 
    typename Enable<T, Type const&>::type 
    get(size_t i) const { return m_elements.at(i); } 

    // You'd better declare a whole lot of other methods if you really want that 
    // like empty, size and clear at the very least. 
    // Just use the same construct for the return type. 

protected: 
    vector() : m_elements() {} 
    vector(vector const& rhs) : m_elements(rhs.m_elements) {} 
    vector& operator=(vector const& rhs) { m_elements = rhs.m_elements; return *this; } 
    ~vector() {} // Not virtual, because cannot be invoked publicly :) 

private: 
    std::vector<Type> m_elements; // at() is inefficient on lists 
}; 

Как это работает?

В основном, когда вы вызываете get<Name> компилятор имеет 2 варианта:

  • vector<Name,std::string>::get
  • vector<Foo,int>::get

Теперь, благодаря enable_if, второй вариант плохо сформированным (тип выводится не может используйте, потому что Foo! = Name), и, таким образом, благодаря SFINAE, эта альтернатива исключается из списка без каких-либо претензий.

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


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

template <class Tag, class Embedded> 
class Embed 
{ 
// redeclares same private and protected interface 
public: 
    template <class T> 
    typename Enable<T,Embedded &>::type get() { return m_element; } 

    template <class T> 
    typename Enable<T,Embedded const&>::type get() const { return m_element; } 

private: 
    Embedded m_element; 
}; 

Тогда вы используете его так:

class my_class: public Embed< Names, std::vector<std::string> >, 
       public Embed<Foo,int> 
{ 
}; 

my_class a; 
std::vector<std::string> const& names = a.get<Names>(); 
int foo = a.get<Foo>(); 

a.get<Names>().push_back("Peter"); 

Это проще, так как вы только обеспечить аксессоров и не должны написать целую кучу методов просто переслать работу.


И теперь, когда мы так много работать, мы должны спросить себя: это, кажется, весьма практичным и универсальным, конечно, есть библиотека или что-то?

Существует >>Boost.Fusion's map:

class my_class 
{ 
public: 
    template <class Tag> 
    typename result_of::at_key<map_type, T>::type & 
    get() { return at_key<T>(m_data); } 

    template <class Tag> 
    typename result_of::at_key<map_type, T>::type const& 
    get() const { return at_key<T>(m_data); } 

private: 
    // First element of the pair: TAG 
    // Second element of the pair: Actual type of the data 
    typedef boost::fusion::map < 
    std::pair<Name, std::vector<std::string> >, 
    std::pair<Foo, int> 
    > map_type; 

    map_type m_data; 
}; 
+0

Вау! Это решение очень сложно. К сожалению, это не слишком коммуникабельно из-за его сложности. Имеете ли имя и Foo существующие типы данных? – phlipsy

+0

Очень сложный, но зачем использовать наследование? –

+0

@Anton: Чтобы добавить интерфейс списка * непосредственно * к вашему классу. Не забывайте, в конце концов: я бы не писал так, но хочу убедить оригинального автора макроса. Еще один «слой косвенности» не очень полезен. – phlipsy

1

Основная проблема с макроса: он решает проблему вы действительно не имеете.

Создает класс со списком, в котором списки обрабатываются напрямую. Поскольку мы думаем, что в OO члены должны быть инкапсулированы, и мы хотим использовать DRY, мы приходим к этой конструкции, а my_class остается действительно классом данных.

Если все классы должны делать, это содержит списки, превращать их в структуру и сохранять списки в открытом доступе. Таким образом, у вас есть чистое намерение, и в списки можно получить доступ к STL-пути. Если класс должен иметь контроль над списками, вам не следует раскрывать списки, и макросы мало полезны.

Так что мой код будет (не компилируется):

struct my_class { 
    std::list<int> integers; 
    std::list<std::string> names; 
    // ... 
}; 

int main() 
{ 
    my_class lists; 
    lists.integers.push_back(5); 
    size_t size_names = lists.names.size(); 
} 

Pro:

  1. легко добавлять списки
  2. нет дублирования кода
  3. последовательный интерфейс (STL)
  4. просто
  5. no macro's

Con:

  1. нет инкапсуляции данных, если это было бы требование
+0

«это решение проблемы, которой у вас действительно нет». Большой! И даже если я хочу инкапсулировать свои данные, я могу написать класс обертки, как предложил Мартин Йорк. – phlipsy

+0

На самом деле, в решении Мартина, члены также не инкапсулированы, но он предоставил пользовательский интерфейс (add, get), как это делают макросы. Это сводится к пониманию правильной практики кодирования и определению того, что было бы лучше для вашей ситуации. – stefaanv