c++类,它可以容纳从一个公共类继承而来的一组类中的一个

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

本文关键字:一个 继承 一组 c++      更新时间:2023-10-16

c++中如何处理拥有另一个类实例所有权的类,而该实例可能是继承自一个公共类的多个类的实例?

的例子:

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

假设还有一个类Container,每个类都包含一个Item,并且任何人对Item感兴趣的唯一时间是当他们将其从容器中取出时。我们也可以说,item只在Container创建的同时创建,目的是将它们放入Container中。

有哪些不同的构造方法?我们可以在Container中为所包含的Item创建一个指针,然后将参数传递给Container的构造函数,以确定要在哪种类型的Item上调用new,这样就可以将Item全部粘贴到堆中。是否有一种方法可以将Item与Container一起存储在堆栈中,这是否有任何优点?

如果Container和Items是不可变的,我们在创建时就知道它们的一切,并且永远不会改变它们,这有什么不同吗?

正确的解决方案如下:

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

如果你有一个旧的编译器,你可以使用std::auto_ptr代替。

智能指针确保容器严格拥有该项。(你也可以把它变成一个普通的指针,并卷起你自己的析构函数/赋值op/复制/移动/移动赋值op/等,但unique_ptr已经完成了这一切,所以…)

为什么你需要在这里使用一个指针,而不仅仅是一个普通的组合?

因为如果你编写,那么你必须知道要编写的确切类。你不能引入多态性。所有Container对象的大小必须相同,Item的派生类的大小可能不同。

如果你迫切需要作曲呢?

那么你需要尽可能多的Container变体,因为有存储的项目,因为每个这样的容器将是不同的大小,所以它是一个不同的类。你最好的机会是:

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

好吧,疯狂的想法。不要用这个:

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

容器本身不需要动态分配,您只需要确保CRAZY_VALUE足够大,可以容纳任何派生类。

下面的示例代码将编译并展示如何执行与您想要执行的操作类似的操作。这就是Java中所谓的接口。请注意,类中至少需要一些相似之处(在本例中是一个通用的函数名)。virtual关键字意味着所有子类都需要实现这个函数,并且无论何时调用该函数,实际调用的都是实类的函数。

类是否为const在这里没有害处。但一般来说,你应该尽可能地保持常量正确。因为如果编译器知道什么是不需要修改的,它可以生成更好的代码。

#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 << "foon"; }
 };
 class bar : public outputter {
   public:
     virtual void print() { std::cout << "barn"; }
 };


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;
}
输出:

   foo
   bar

在容器类中保留一个指针(最好是智能指针),并在需要复制时调用由派生类实现的Item类的纯虚拟clone()成员函数。您可以用一种完全通用的方式来完成此操作,例如:

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> { /* ... */ };

对于堆栈存储,您可以使用重载的new调用alloca(),但这样做会有风险。只有当编译器内联了特殊的new操作符时,它才会起作用,你不能强迫它这样做(除非使用不可移植的编译器pragmas)。我的建议是,不值得为此而生气;运行时多态性属于堆。