开发者

C++ class that can hold one of a set of classes that all inherit from a common class

What are the ways in C++ to handle a class that has ownership of an instance of another class, where that instance could potentially be of a number of classes all of which inherit from a common class?

Example:

class Item { //the common ancestor, which is never used directly
public:
  int size;
}

class ItemWidget: public Item { //possible class 1
public:
  int height;
  int width;
}

class ItemText: public Item { //possible class 2
  std::string text;
}

Let's say there is also a class Container, each of which contains a single Item, and the only time anyone is ever interested in an Item is when they are getting it out of the Container. Let's also say Items are only created at the same time the Container is created, for the purpose of putting them in the Container.

What are the different ways to structure this? We could make a pointer in Container for the contained Item, and then pass argu开发者_JAVA百科ments to the constructor of Container for what sort of Item to call new on, and this will stick the Items all in the heap. Is there a way to store the Item in the stack with the Container, and would this have any advantages?

Does it make a difference if the Container and Items are immutable, and we know everything about them at the moment of creation, and will never change them?


A correct solution looks like:

class Container {
public:
    /* ctor, accessors */
private:
    std::unique_ptr<Item> item;
};

If you have an old compiler, you can use std::auto_ptr instead.

The smart pointer ensures strict ownership of the item by the container. (You could as well make it a plain pointer and roll up your own destructor/assignment op/copy ctor/move ctor/ move assignment op/ etc, but unique_ptr has it all already done, so...)

Why do you need to use a pointer here, not just a plain composition?

Because if you compose, then you must know the exact class which is going to be composed. You can't introduce polymorphism. Also the size of all Container objects must be the same, and the size of Item's derived classes may vary.

And if you desperately need to compose?

Then you need as many variants of Container as there are the items stored, since every such Container will be of different size, so it's a different class. Your best shot is:

struct IContainer {
    virtual Item& getItem() = 0;
};

template<typename ItemType>
struct Container : IContainer {
    virtual Item& getItem() {
        return m_item;
    }
private:
    ItemType m_item;
};


OK, crazy idea. Don't use this:

class AutoContainer
{
  char buf[CRAZY_VALUE];
  Base * p;

public:

  template <typename T> AutoContainer(const T & x)
    : p(::new (buf) T(x))
  {
    static_assert(std::is_base_of<Base, T>::value, "Invalid use of AutoContainer");
    static_assert(sizeof(T) <= CRAZY_VAL, "Not enough memory for derived class.");
#ifdef __GNUC__
    static_assert(__has_virtual_destructor(Base), "Base must have virtual destructor!");
#endif
  }

  ~AutoContainer() { p->~Base(); }

  Base & get() { return *p; }
  const Base & get() const { return *p; }
};

The container requires no dynamic allocation itself, you must only ensure that CRAZY_VALUE is big enough to hold any derived class.


the example code below compiles and shows how to do something similar to what you want to do. this is what in java would be called interfaces. see that you need at least some similarity in the classes (a common function name in this case). The virtual keyword means that all subclasses need to implement this function and whenever that function is called the function of the real class is actually called.

whether the classes are const or not doesn't harm here. but in general you should be as const correct as possible. because the compiler can generate better code if it knows what will not be changed.

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

class outputter {
  public:
    virtual void print() = 0;
};

class foo : public outputter {
  public:
    virtual void print() { std::cout << "foo\n"; }
 };

 class bar : public outputter {
   public:
     virtual void print() { std::cout << "bar\n"; }
 };




int main(){
  std::vector<outputter *> vec;
  foo *f = new foo;
  vec.push_back(f);
  bar *b = new bar ;
  vec.push_back(b);
  for ( std::vector<outputter *>::iterator i = 
        vec.begin(); i != vec.end(); ++i )
  { 
    (*i)->print();
  }   
  return 0;
}

Output:

   foo
   bar


Hold a pointer (preferably a smart one) in the container class, and call a pure virtual clone() member function on the Item class that is implemented by the derived classes when you need to copy. You can do this in a completely generic way, thus:

class Item {
    // ...
private:
    virtual Item* clone() const = 0;
    friend Container; // Or make clone() public.
};

template <class I>
class ItemCloneMixin : public Item {
private:
    I* clone() const { return new I(static_cast<const I&>(*this); }
};

class ItemWidget : public ItemCloneMixin<ItemWidget> { /* ... */ };
class ItemText : public ItemCloneMixin<ItemText> { /* ... */ };

Regarding stack storage, you can use an overloaded new that calls alloca(), but do so at your peril. It will only work if the compiler inlines your special new operator, which you can't force it to do (except with non-portable compiler pragmas). My advice is that it just isn't worth the aggravation; runtime polymorphism belongs on the heap.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜