store two different classes (with same inherited base class) in same vector? (no boost)
Asked Answered
D

6

2

I have two different classes (First, Second) which inherit the same base class (Base). I would like to store an instance of First and Second in the same vector, without their classes being spliced down to the Base class. If I use vector, this splicing will occur, like following:

#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"
}

Ultimately, when the two objects are put in to vector, they are spliced. Is there a way (without boost) to COPY both of these objects in to the same vector? A vector is not what I am looking for, I want to copy the objects.


There were a lot of good answers here. Unfortunately, I can't assume C++11, so I ended up leveraging the clone constructor with the hint at storing pointers. But ultimately, the clone allows me to create the copy first.

Here was my final solution:

#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();

}
Downwind answered 21/12, 2011 at 7:27 Comment(2)
I think you mean 'sliced'. 'Spliced' is kind of the opposite.Schroeder
Just be sure that when using clone() without smart-pointers, to delete everything you clonedInnovate
I
3

To copy inherited objects without slicing, you need to define a clone() function in the base class and override in the derived classes:

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

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

This way, even with a pointer to base class, you will get a copy of the derived class.

Innovate answered 21/12, 2011 at 7:52 Comment(0)
D
2

Yes, you create a vector of pointers to Base:

std::vector<Base*> objs;

or, better yet, a vector of smart pointers to the base class, so you don't need to worry about memory management.

Disharmony answered 21/12, 2011 at 7:29 Comment(3)
I am looking to copy the objects, sorry for not clarifying.Downwind
@Downwind I don't understand. If you want to copy objects, have a copy constructor...Disharmony
@gnychis: then deep copy the pointers. Otherwise it cannot be doneSesquialtera
C
2

I don't know why you want to copy the objects, but if it is for easy memory management then this will do :

std::vector<std::shared_ptr<Base> > objs;
objs.push_back(std::make_shared<First>());
objs.push_back(std::make_shared<Second>());
Consecration answered 21/12, 2011 at 7:51 Comment(3)
Note: std::make_shared is c++11 and not available in c++03Decolorize
@mark: std::shared_ptr is also not available in C++03. The point is OP can solve his problem with a smart pointer. The namespace of the smart pointer is irrelevant. He can use boost:: too.Consecration
@sad_man: The OP titled its post with "(no boost)". And no, there is no std::shared_ptr in C++03 (apart some experimental extesions: shared_ptr has been ratified as "standard" in C++11, essentially by rewriting boost::shared_ptr using C++11 specific features)Thermion
T
2

By the way you formulate the question, it is something impossible: vetor<Base> holds a sequence of Bases, while First and Second are both Base plus something more.

By it's very nature, vector contains sequence of identical objects (all Base) and push_back, actually doesn't place the object inside the vector, but copies the object into the one that is constructed at the end of the vector (and is a Base object, hence only the Base subcomponent is copyed).

To solve the problem, you have to make your vector not to "contain" Base-s but to "refer to" Base-s. That requires pointers

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

But this requires also memory management. This can be done in merely two ways:

1) use some smart-pointer that "owns" the base. In C++11 there is std::shared_ptr, and std::unique_ptr. In C++03 you have to arrange yourself a reference-counting pointer like

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; 
};

and declare your vector as

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

Destroying v, will destroy the ptrs that will in turn destroy the objects. NOTE: Base must also declare a virtual destructor, otherwise derived object cannot be plymorphically destroyed.

2) Alternatively, you can "customize" the vector to "own" the pointer itself. this can be obtained by something like

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);

NOTE: Base still need virtual destructor. Also, being std::vector and ownvector not polymorphic on destruction, don't use themselves as polymorphic types.

3) In case you are doing everything on_stack, you can very easily do

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.
}
Thermion answered 21/12, 2011 at 8:31 Comment(0)
W
1

redifine copy constractor, then use :

std::vector<Base*> objs;
objs.push_back(new First(*f));
objs.push_back(new Second(*s));
Waterish answered 21/12, 2011 at 7:33 Comment(0)
H
1

I'm intreged by your saying that you want to copy your objects. Generally, polymorphism and copy/assignment don't work well together, but you can do it by using the letter-envelop idiom. Something like:

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; }
};

This results in a class Base which behaves polymorphically, but supports copy and assignment (and assignment may change the effective type), and won't slice. It also results in a serious performance hit, and usually isn't what is wanted anyway—in my experience, most polymorphic classes have identity, and shouldn't be copyable or assignable. But it is a possible solution, and can be useful in some specific cases.

Hornbook answered 21/12, 2011 at 12:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.