Контейнер для ограниченной группы классов - PullRequest
0 голосов
/ 09 декабря 2011

РЕДАКТИРОВАТЬ:

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

Короче говоря: Мой вопрос заключался в том, как создать контейнер с ограниченным указателем, который может содержать только класс из ограниченной группы классов.Я назвал это enum , и это было большой ошибкой.Как я уже писал, полиморфизм - это не главная проблема, а лишь иллюстрация.

Ответы [ 3 ]

2 голосов
/ 11 декабря 2011

Как я уже сказал в своем комментарии, вы можете сделать это с помощью Boost.Variant . Есть и другой путь, более естественный, и это обычная иерархия классов с полиморфизмом.

Поскольку уже есть ответы на версию иерархии, я собираюсь сосредоточиться на варианте варианта здесь.

Сначала код, потом объяснение:

#include <iostream>
#include <boost/variant.hpp>

struct Cat{
  void speak() const{ std::cout << "meow\n"; }
  void cat_extra() const{ std::cout << "Special cat move!\n"; }
  ~Cat(){ std::cout << "Cat died\n"; }
};

struct Dog{
  void speak() const{ std::cout << "wuff\n"; }
  void dog_extra() const{ std::cout << "Special dog move!\n"; }
  ~Dog(){ std::cout << "Dog died\n"; }
};

struct Bird{
  void speak() const{ std::cout << "chirp\n"; }
  void bird_extra() const{ std::cout << "Special bird move!\n"; }
  ~Bird(){ std::cout << "Bird died\n"; }
};

struct Fish{
  void speak() const{ std::cout << "blub\n"; }
  void fish_extra() const{ std::cout << "Special fish move!\n"; }
  ~Fish(){ std::cout << "Fish died\n"; }
};

struct speak_visitor
    : boost::static_visitor<void>
{
    template<class Animal>
    void operator()(Animal const* p) const{
        p->speak();
    }
};

struct extra_visitor
    : boost::static_visitor<void>
{
    void operator()(Cat const* p) const{
        p->cat_extra();
    }
    void operator()(Dog const* p) const{
        p->dog_extra();
    }
    void operator()(Bird const* p) const{
        p->bird_extra();
    }
    void operator()(Fish const* p) const{
        p->fish_extra();
    }
};

struct delete_visitor
    : boost::static_visitor<void>
{
    template<class Animal>
    void operator()(Animal const* p) const{
        delete p;
    }
};

int main(){
    typedef boost::variant<Cat*, Dog*, Bird*, Fish*> variant_type;
    variant_type var;
    speak_visitor sv;
    delete_visitor dv;
    extra_visitor ev;

    var = new Cat();
    var.apply_visitor(sv);
    var.apply_visitor(ev);
    var.apply_visitor(dv);

    var = new Dog();
    var.apply_visitor(sv);
    var.apply_visitor(ev);
    var.apply_visitor(dv);

    var = new Bird();
    var.apply_visitor(sv);
    var.apply_visitor(ev);
    var.apply_visitor(dv);

    var = new Fish();
    var.apply_visitor(sv);
    var.apply_visitor(ev);
    var.apply_visitor(dv);
}

Пример запуска на Ideone .

Теперь перейдем к объяснению. A boost::variant в основном является расширенным и помеченным union. A union может хранить разные вещи, но только по одному за раз. То же самое касается варианта. boost::variant помечен, потому что вы можете узнать, какой тип хранится в данный момент, с помощью функции-члена which, которая будет возвращать индекс на основе 0 в зависимости от того, какой тип хранится в данный момент. Связанная документация объяснит вариант лучше, чем я могу.

Для безопасного «посещения» сохраненного значения требуется конструкция с именем «посетитель». Идея для посетителей великолепна, и есть много ресурсов, которые ее описывают. Связанная документация также входит в некоторые детали здесь. По существу, вариант внутренне выполняет switch для сохраненного типа (с which) и вызывает вариант operator() с соответствующим аргументом, что-то вроде этого:

// something similar to this happens internally in `apply_visitor`
// contrived for our example classes here
template<class V>
void variant::apply_visitor(V& v){
  switch(this->which()){
    case 0: v((Cat*)&(this->internal_storage)); break;
    case 1: v((Dog*)&(this->internal_storage)); break;
    case 2: v((Bird*)&(this->internal_storage)); break;
    case 3: v((Fish*)&(this->internal_storage)); break;
  }
}

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

Эта концепция может быть не совсем понятна при использовании общих посетителей (speak_visitor, delete_visitor), но она разъясняется extra_visitor. Если вы закомментируете там одну из operator() перегрузок, компилятор будет раздражен.

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

2 голосов
/ 09 декабря 2011

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

Похоже, что вы пытаетесь сделать это.

class Animal
{
    virtual void jump() = 0;
};

class Dog : public Animal
{
    virtual void jump() { do stuff here; }
    void bark() { other stuff; } // method peculiar to dogs
};

class Cat : public Animal
{
    virtual void jump() { do stuff here; }
};

void main()
{
    Animal *a = new Dog(); // abstract types have to be referred to by pointers
    a->jump();
    delete a;

    Dog d;
    d.bark(); // if the compiler knows it's a dog, it can do dog things
    Animal *b = &d;
    b->jump(); // although b came from d, it's an animal so can only jump not bark
}

Теперь вам действительно нужно знать, является ли Animal Dog илиCat во время выполнения?Это нарушит инкапсуляцию - возможно, вы подкласс класса Animal, потому что вы хотите скрыть тип Animal, а не раскрывать его.

Если это так, вы можете использовать dynamic_cast и / или идентификацию типа во время выполнения http://en.wikipedia.org/wiki/Typeid, которая скажет вам, является ли каждый класс кошкой или собакой.

1 голос
/ 09 декабря 2011

Почему вы хотите перечисление?Почему бы не сделать это?

class animal {public: virtual void jump() = 0;}; 
class DOG:public animal{public: virtual void dog::jump(){}}; 
class CAT:public animal{public: virtual void cat::jump(){}}; 
DOG dog; CAT cat;
animal &a1 = dog; // Using a reference
a1.jump();
animal *a2 = &dog; // Using a pointer
a2->jump();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...