【问题标题】:C++ std containers - polymorphism without pointers. Is it possible?C++ std 容器 - 没有指针的多态性。可能吗?
【发布时间】:2014-08-09 09:18:00
【问题描述】:

是否可以在 any std c++ 容器中维护派生类的知识,而无需使用指针,从容器中动态转换返回值?我知道我可以创建一个向量或某种基类类型的指针,并让它们保留它们的子类。但问题是我必须使用指针吗?

例子:

struct A {
  int x = 0, y = 0, z = 0;
  virtual void foo() { cout << "A" << endl; };
};

struct B : public A {
  int a = 1, b = 1, c = 1;
  virtual void foo() { cout << "B" << endl; };
};

int main() {  
  <SOMECONTAINER><A> a(2);
  a[0] = A();
  a[1] = B();
  B * p;
  B& n = dynamic_cast<B&>(a[1]); // Always throws?
  p = dynamic_cast<B*>(&a[1]); // Always zero?
  cout << p << endl;
}

【问题讨论】:

  • 可以避免使用间接,例如具有变体数据类型;通过在每个元素旁边存储对象大小并在迭代时动态转到下一个元素(标准库容器不支持),也可以浪费更少的空间。
  • 我强烈推荐观看Inheritance is the Base Class of Evil。 Sean Parent 描述了如何实现类似于std::vector&lt;Animal&gt; v{Cat{}, Dog{}}; v[0].makeSound(); v[1].makeSound(); 打印Meow! Woof! 的效果。所讨论的CatDog 类只需要一个名为makeSound 的函数(成员或免费),该函数将该类作为参数。这些类不是从 Animal 派生的。
  • @ghostofstandardspast 如果我没记错的话,肖恩的方法仍然依赖于使用虚拟调度和动态分配。 (如果我的记忆不正确,请告诉我我是个白痴。)他只是使用持有者类将其隐藏为实现细节。这种方法非常好,但如果目标是避免动态分配,它就会失败。
  • 您可以定义与 C++ 容器一起使用的(受限)变体数据类型。但是,您不能做的是将不同大小的对象放入标准库容器中。 (受限)变体数据类型通常以联合形式实现,因此具有它们可以包含的最大可能类型的大小。
  • @ChrisHayden,是的,仍然隐藏了虚函数的使用,但据我所知,OP 只是希望能够拥有一个“基类”对象的容器,而不是指针。

标签: c++ pointers c++11


【解决方案1】:

是的,您必须使用指针。否则,尝试将B 放入A 的容器中会导致slicingB 被缩减为A(这不仅限于容器,如果您这样做,也会发生完全相同的事情A a = B() 或者如果您将 B 传递给期望 A 的函数。

当您稍后将其取出时,它是一个 A,完全不知道它的血统包括 B 类型的杰出祖先——无论您以何种方式看待 A,您都可以不要让它成为B

【讨论】:

  • 更多信息:您可以使用智能指针,以避免一些普通指针的陷阱。引用也保持多态行为,但您不能拥有引用容器。
  • 使用boost::variant 之类的东西怎么样?不需要指针。
  • 在不使用指针的情况下存储任何适用(子)类的对象,正如 OP 所希望的那样。
  • 好吧,我们可以很容易地为从同一个基础对象派生的对象编写一个容器,这些对象具有指定的最大大小和对齐要求,而无需写下所有可以保存的类型。这会(大部分)推翻这种反对意见。
  • @jon 但有人可能想知道是否有可能找到这个问题,那么为什么不回答呢? ;)
【解决方案2】:

我将忽略对齐,或者更确切地说,假设指针之后的数据已充分对齐。

template<class T, unsigned N>
struct poly_anna;
template<class T,unsigned N>
struct poly_bob {
  typedef poly_anna<T,N> poly_anna_;
  T*(*get)(poly_anna_*) = nullptr;
  void(*destroy)(poly_anna_*) = nullptr;
  void(*move_to)(poly_anna_ *,poly_anna_*) = nullptr;
  void(*copy_to)(poly_anna_ const*, poly_anna_*)=nullptr;
};

template<class T, unsigned N>
struct poly_anna {
private:
  poly_bob<T,N> const*bob=nullptr;
  char buff[N];
public:
  template<class U> static poly_bob<T,N> const* get_bob() {
    static poly_bob<T,N> b={
      [](poly_anna*a)->T&{ return *(U*)&a->buff[0]; },
      [](poly_anna*a){ ((U*)&a->buff[0])->~U(); a->bob = nullptr; },
      [](poly_anna*s,poly_anna*d){
        if (s->bob==d->bob){
          *((U*)&d->buff[0])=std::move(*((U*)&d->buff[0]));
          return;
        }
        if (d->bob != nullptr) {
          d->bob->destroy(b);
        }
        d->store( std::move( *(U*)&s->buff[0] ) );
      },
      [](poly_anna const* s, poly_anna*d){
        if (d->bob == s->bob){
          *(U*)&d->buff[0] = *(U const*)&s->buff[0];
          return;
        }
        if (d->bob){ d->bob->destroy(d); }
        d->store( *(U const*)*s->buff[0] );
      }
    };
    return &b;
  };
  template<class U_>
  void store(U_&& u){
    typedef typename std::decay<U_>::type U;
    static_assert( sizeof(U)<=N, "N not large enough" );
    if (bob) bob->destroy( this );
    bob = get_bob<U>();
    new (&buff[0]) U( std::forward<U_>(u) );
  }
  void reset(){ if (bob) bob->destroy(this); }
  T& get() {
    return bob->get(this);
   }
  T const& get() const {
    return bob->get(const_cast<poly_anna*>(this));
   }
   poly_anna( poly_anna const& o ){
     if (o.bob) o.bob->copy_to( &o, this );
   }
   poly_anna( poly_anna && o ){
     if (o.bob) o.bob->move_to( &o, this );
   }
   poly_anna&operator=( poly_anna const& o ){
     if (o.bob) o.bob->copy_to( &o, this );
     else if (bob) bob->destroy(this);
     return *this
   }
   poly_anna&operator=( poly_anna && o ){
     if (o.bob) o.bob->move_to( &o, this );
     else if (bob) bob->destroy(this);
     return *this
   }
   poly_anna()=default;
   ~poly_anna(){if(bob)bob->destroy(this);}
   explicit operator bool()const{return bob;}
};

这是我对多态变体的尝试。它存储TT 的子级,只要它们不大于N 并且可以存储在std 容器中。

如果编译成功,请告诉我。

【讨论】:

    【解决方案3】:

    您需要用于虚拟成员函数调度的指针。

    当您考虑它时,如果没有指针,您就会“按值”。值语义和和多态在一起并没有真正的意义。

    一个有一个上下文/类型。它是原子的和简单的。所见即所得,可以这么说。当然,你可以施放它,但是你有……另一个价值。

    【讨论】:

    • nitpick:当然,在这个问题的范围内,你是正确的,但不是全局的。我们不需要“虚拟成员函数分派的指针”,因为引用与虚拟分派的来源一样好。如果要通过存储在容器中的对象集合来实现多态性,则“需要”指针,因为容器不能包含引用。许多想要虚拟调度的人希望通过容器来实现,但是说虚拟调度严格要求指针仍然是简化的。不是你的意思:)
    【解决方案4】:

    有一句经常被引用的程序员谚语:没有什么问题是用额外的间接层解决不了的,除了太多的间接层。

    付诸实践,看看boost::variant
    如果您的容器存储 boost::variants 允许您要存储的所有(子)类,则可以避免使用指针。

    这可能是一场胜利,但不一定是。
    在您承诺此类解决方案之前进行衡量。

    【讨论】:

      【解决方案5】:

      我觉得这里其实有两个问题:

      1. 是否可以在不使用指针和相关动态分配的情况下在 STL 容器中获得多态语义?
      2. 是否可以保留存储在 STL 容器中的多态对象的具体类型?

      1 的答案是肯定的,但需要付出一些努力。正如一些人所提到的,一种方法是使用像boost::variant 这样的变体类型。这种方法的问题是您失去了与存储在变体中的对象自然交互的能力,而不得不编写访问者,这有很多语法开销。

      如果类层次结构是有界的,那么更好的方法可能是使用专门设计用于保留多态语义及其相关语法的 boost::variant(没有双关语)的变体。一个例子可能是emplacer。如上面 dyp 的评论中所述,emplacer 是一个受限制的变体。

      至于问题 2,如果不使用 typeid() 或手动类型系统,我不知道有什么方法可以做到这一点。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-05-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-05-10
        • 1970-01-01
        • 2011-01-14
        • 1970-01-01
        相关资源
        最近更新 更多