хранить два разных класса (с одним и тем же унаследованным базовым классом) в одном и том же векторе? (без повышения) - PullRequest
2 голосов
/ 21 декабря 2011

У меня есть два разных класса (первый, второй), которые наследуют один и тот же базовый класс (базовый). Я хотел бы хранить экземпляры First и Second в одном и том же векторе, не объединяя их классы с базовым классом. Если я использую вектор, это соединение произойдет, как показано ниже:

#include <iostream>
#include <vector>

class Base {
  public:
    Base() { }
    virtual void test() { std::cout << "I am just the base class\n"; }
};

class First : public Base {
  public:
    First() { }
    void test() { std::cout << "This is the First class\n"; }
};

class Second : public Base {
  public:
    Second() { }
    void test() { std::cout << "This is the Second class\n"; }
};

int main() {
  First *f = new First();
  Second *s = new Second();

  // First, I keep a vector of pointers to their base class
  std::vector<Base> objs;
  objs.push_back(*f);
  objs.push_back(*s);
  objs[0].test();   // outputs "I am just the base class"
  objs[1].test();   // outputs "I am just the base class"
}

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


Здесь было много хороших ответов. К сожалению, я не могу принять C ++ 11, поэтому я в итоге использовал конструктор клонов с подсказкой для хранения указателей. Но в конечном итоге клон позволяет мне сначала создать копию.

Вот мое окончательное решение:

#include <iostream>
#include <vector>

class Base {
  public:
    Base() { }
    virtual void test() { std::cout << "I am just the base class\n"; }
    virtual Base* clone() const = 0;
};

class First : public Base {
  public:
    First() { }
    void test() { std::cout << "This is the First class\n"; }
    First* clone() const { return new First(*this); }
};

class Second : public Base {
  public:
    Second() { }
    void test() { std::cout << "This is the Second class\n"; }
    Second* clone() const { return new Second(*this); }
};

int main() {
  First *f = new First();
  Second *s = new Second();

  std::vector<Base *> bak;
  bak.push_back(f->clone());

  bak[0]->test();

}

Ответы [ 6 ]

3 голосов
/ 21 декабря 2011

Чтобы скопировать унаследованные объекты без нарезки, вам нужно определить функцию clone () в базовом классе и переопределить в производных классах:

class Base
{
public:
  Base* clone() const = 0;
}

class Derived
{  
  Derived* clone() const { return new Derived(*this); }
}

Таким образом, даже с указателем на базовый класс,вы получите копию производного класса.

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

По тому, как вы формулируете вопрос, это нечто невозможное: vetor<Base> содержит последовательность Base с, в то время как First и Second оба Base плюс что-то большее.

По своей природе вектор содержит последовательность идентичных объектов (все базы) и push_back, фактически не помещает объект внутри вектора, но копирует объект в тот, который построен в конце вектора (и является Base объектом, поэтому копируется только подкомпонент Base).

Чтобы решить эту проблему, вы должны сделать так, чтобы ваш вектор «не содержал» Base -s, а «ссылался на» Base-s. Это требует указателей

std::vector<Base*> v;
v.push_back(new First);
v.push_back(new Second);

Но для этого также необходимо управление памятью. Это можно сделать двумя способами:

1) использовать какой-нибудь смарт-указатель, который «владеет» базой. В C ++ 11 есть std::shared_ptr и std::unique_ptr. В C ++ 03 вы должны расположить указатель подсчета ссылок как

template<class T>
class ptr
{
public:
  ptr() :p(), c() {}
  explicit ptr(T* z) :p(z), c(new unsigned(1)) {}
  ptr(const ptr& s) :p(s.p), c(s.c) { if(c) ++*c; }
  ptr& operator=(const ptr& s) 
  { 
    if(this!=&s) 
    { 
      if(c && !--*c) { delete p; delete c; }
      p = s.p; c=s.c;
      if(c) ++*c;
    }
    return *this;
  }
  ~ptr() { if(c && !--*c) { delete p; delete c; } }

  T* operator->() const { return p; }
  T& operator*() const { return *p; }

private:
  T* p;
  unsigned* c; 
};

и объявите свой вектор как

std::vector<ptr<Base> > v;
v.push_back(ptr(new First));
v.push_back(ptr(new Second));
v[0]->test();
v[1]->test();

Уничтожение v, уничтожит ptrs, которые, в свою очередь, уничтожат объекты. ПРИМЕЧАНИЕ: База также должна объявить виртуальный деструктор , иначе производный объект не может быть плиморфно уничтожен.

2) В качестве альтернативы, вы можете «настроить» вектор на «собственный» указатель. это может быть получено чем-то вроде

template<class T>
class owningvector: public std::vector<T*>
{
public:
  ~owningvector()
  {
    for(size_t i=0; i<size(); ++i)
      delete at(i);
  }
  owningvector() {}
private:
  //disable vector copy and assign
  owningvector(const owningvector&);
  owningvector& operator=(const owningvector&);
};

owningvector<Base> ov;
ov.push_back(new First);
ov.push_back(new Second);

ПРИМЕЧАНИЕ: Base все еще нужен виртуальный деструктор. Кроме того, будучи std::vector и ownvector не полиморфными при разрушении, не используйте себя как полиморфные типы.

* 1 042 * 3) Если вы все делаете на_стаке, вы можете очень легко сделать
int main()
{
  First first;
  Second second;
  std::vector<Base*> v;
  v.push_back(&first);
  v.push_back(&second);

  v[0]->test();
  v[1]->test();

  //no other memory management is required:
  // v is destroyed without doing anything special.
  // second and first are destroyed just further.
}
2 голосов
/ 21 декабря 2011

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

std::vector<std::shared_ptr<Base> > objs;
objs.push_back(std::make_shared<First>());
objs.push_back(std::make_shared<Second>());
2 голосов
/ 21 декабря 2011

Да, вы создаете vector указателей на Base:

std::vector<Base*> objs;

или, что еще лучше, vector интеллектуальных указателей на базовый класс, так что вам не нужно беспокоиться об управлении памятью.

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

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

class Base
{
    Base* myImpl;
    virtual Base* clone() const
    {
        //  or perhaps just abort, since this one shouldn't
        //  ever be called in practice.
        return new Base( myImpl->clone() );
    }
protected:
    Base() : myImpl( NULL ) {}
    Base( Base* impl ) : myImpl( impl ) {}

public:
    Base( Base const& other ) : myImpl( other.myImpl->clone() ) {}
    virtual ~Base() {}
    virtual void test() { myImpl->test(); }
};

class First : public Base
{
    virtual Base* clone() const
    {
        return new First( *this );
    }

    First() {}
public:
    static Base createInstance() { return Base( new First ); }
    virtual void test() { std::cout << "In First" << std::endl; }
};

class Second : public Base
{
    virtual Base* clone() const
    {
        return new Second( *this );
    }

    Second() {}
public:
    static Base createInstance() { return Base( new Second ); }
    virtual void test() { std::cout << "In Second" << std::endl; }
};

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

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

редифинируйте копию, затем используйте:

std::vector<Base*> objs;
objs.push_back(new First(*f));
objs.push_back(new Second(*s));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...