Objects of different classes in a single vector?
In my code, I have a set of objects:
class Sphere { ...
class Plane { ...
...
And I need to use a collection of them (they will all have 开发者_如何学运维different types) in a vector
. How would I add objects of different classes to a vector
?
Sphere and Plane would need a common base type, or your vector would need to be composed of void*
's.
Common base type (better):
class Shape { ... };
class Sphere : public Shape { ... };
class Plane : public Shape { ... };
std::vector<Shape*> shapes;
or void*
's (not great):
std::vector<void*> shapes;
The classes would need to have a common base class, e.g.:
class MyBase { };
class Sphere : public MyBase { };
class Plane : public MyBase { };
Then in order to store polymorphic objects in a vector, you must store a pointer to them (because they can be different sizes from the base class). I recommend using a std::shared_ptr<MyBase>
or std::unique_ptr<MyBase>
(or use Boost if C++0x isn't available).
std::vector<std::shared_ptr<MyBase> > v;
v.push_back<std::shared_ptr<MyBase>(new Sphere());
v.push_back<std::shared_ptr<MyBase>(new Plane());
If there is no common base, you'd have to use void*
, or find a different way to do this.
Creating containers of polymorphic types is a classical solutions, which comes with its own problems. One of which the types have to become polymorphic just in order to add them to a container -- not a good reason. Another problem is early and tight coupling resulting in more difficult maintenance and lack of flexibility, just in order to add them to a container -- not a good reason. Fortunately, in C++ there are better alternatives.
A better solution would be storing functions and not objects themselves in containers. The common reason why you want to put different types in the same container is to perform the same actions on all of them, for example, Sphere::Draw()
or Plane::Draw()
. What you can do is create a container of draw functions instead and erase type. E.g.
vector<function<void()>> drawings;
Sphere s;
Plane p;
drawings.push_back(bind(s, &Sphere::Draw));
drawings.push_back(bind(p, &Plane::Draw));
for(auto I = drawings.begin(); I != drawings.end(); ++i) (*i)();
By doing that you avoided strong coupling and other problems of inheritance, and got a more flexible, more general solution.
The above solution works only with C++11 as it requires std::function()
Class Shape{...code...}
Class Sphere : public Shape{...code...}
Class Plane : public Shape{...code...}
std::vector<Shape*> List;
List.push_back(new Sphere);
List.push_back(new Plane);
or
//Base class to derived class
Shape* Shape_Sphere = new Sphere();
Shape* Shape_Plane = new Plane();
std::vector<Shape*> List;
List.push_back(Shape_Sphere);
List.push_back(Shape_Plane);
and if you want to delete the pointers
std::vector<Shape*>::iterator it;
for(it = List.begin(); it != List.end(); ++it)
{
delete *it;
}
Since the vector stores instances of Shape and Sphere/Plane are derived of the base class Shape, C++ will allow this to work
Are the objects related in a meanginful way? If they're not, then you probably shouldn't.
If they are, you'll want to do some reading on inheritance.
The other posts have told you most of what you need to know. I would like to add that boost has pointer containers that might be handy as they cleanup there contents when they are destroyed. Boost manual
Using std::variant
would be the best solution if you are using C++17. If not, let me explain:
Vector of values is in principle faster than vector of pointers because of smaller cache misses. I investigated this solution and this is the basic idea. Imagine you have three types Parent
, Child1
and Child2
. Size of them are for instance 32 bytes, 40 bytes and 48 bytes. If you create std::vector<char[48]>
, in principle you will be able to hold any of the values in it. And since Child1 and Child2 inherit from Base
, you can access them through Base*
and take advantage of the vtable already present in each class to polymorphically call methods in Child1
and Child2
.
I created a wrapper for std::vector
to do exactly this. Here it is
template <typename Parent, typename... Children>
class polymorphic_vector {
private:
template <typename Base, typename... Others>
class alignas(16) polymorphic {
private:
static constexpr size_t round_to_closest_16(size_t size) {
return ((size % 16) == 0) ? size : ((size / 16) + 1) * 16;
}
template <typename T>
static constexpr size_t get_max_type_size() {
return sizeof(T);
}
template <typename T, typename Arg, typename... Args>
static constexpr size_t get_max_type_size() {
return max(sizeof(T), get_max_type_size<Arg, Args...>());
}
static constexpr size_t max(size_t v1, size_t v2) {
return v1 > v2 ? v1 : v2;
}
class wrapper {
public:
static constexpr int m_size = get_max_type_size<Others...>();
char m_data[m_size];
};
public:
wrapper m_wrapper;
};
using pointer_diff_t = int16_t;
std::vector<polymorphic<Parent, Children...>> m_vector;
std::vector<pointer_diff_t> m_pointer_diff;
template <typename BaseAddr, typename ModifiedAddr>
pointer_diff_t get_pointer_diff(BaseAddr base, ModifiedAddr modified) {
char* base_p = reinterpret_cast<char*>(base);
char* modified_p = reinterpret_cast<char*>(modified);
return base_p - modified_p;
}
template <typename BaseAddr, typename ModifiedAddr>
ModifiedAddr get_modified_addr(BaseAddr base, pointer_diff_t diff) {
char* base_p = static_cast<char*>(base);
return reinterpret_cast<ModifiedAddr>(base_p - diff);
}
public:
polymorphic_vector(int size) : m_vector(size), m_pointer_diff(size) {}
polymorphic_vector() : m_vector(), m_pointer_diff() {}
Parent* get(int index) {
return get_modified_addr<char*, Parent*>(
m_vector[index].m_wrapper.m_data, m_pointer_diff[index]);
}
template <typename Q>
void push_back(const Q& q) {
static_assert(sizeof(Q) <= sizeof(polymorphic<Parent, Children...>));
static_assert(std::is_base_of<Parent, Q>::value);
m_vector.emplace_back();
::new (m_vector.back().m_wrapper.m_data) Q(q);
m_pointer_diff.emplace_back(get_pointer_diff(
m_vector.back().m_wrapper.m_data,
static_cast<Parent*>(
reinterpret_cast<Q*>(m_vector.back().m_wrapper.m_data))));
}
template <typename Q, typename... Args>
void emplace_back(const Args&... args) {
static_assert(sizeof(Q) <= sizeof(polymorphic<Parent, Children...>));
static_assert(std::is_base_of<Parent, Q>::value);
m_vector.emplace_back();
::new (m_vector.back().m_wrapper.m_data) Q(args...);
m_pointer_diff.emplace_back(get_pointer_diff(
m_vector.back().m_wrapper.m_data,
static_cast<Parent*>(
reinterpret_cast<Q*>(m_vector.back().m_wrapper.m_data))));
}
void shuffle() {
std::vector<int> indexes(m_vector.size());
std::iota(indexes.begin(), indexes.end(), 0);
for (int i = 0; i < m_vector.size(); i++) {
std::swap(m_pointer_diff[i], m_pointer_diff[indexes[i]]);
std::swap(m_vector[i], m_vector[indexes[i]]);
}
}
void reserve(int size) { m_vector.reserve(size); }
};
To use it, you need to specify as a template parameter the base class and the size of the internal chunk of memory that would be enough to fit any of the classes you plan to put inside.
Here is an example for Parent
, Child1
and Child2
:
std::vector<Parent, 48> v;
v.emplace_back<Parent>();
v.emplace_back<Child1>(param1, param2);
v.emplace_back<Child2>(param1);
v.get(0)->method1();
v.get(1)->method1();
精彩评论