【问题标题】:Array of polymorphic objects多态对象数组
【发布时间】:2018-09-07 12:32:26
【问题描述】:

我经常遇到需要创建多态对象的数组或向量。我通常更喜欢使用对基类的引用,而不是智能指针,因为它们往往更简单。

数组和向量被禁止包含原始引用,因此我倾向于使用指向基类的智能指针。但是,也可以选择使用std::reference_wrapperhttps://en.cppreference.com/w/cpp/utility/functional/reference_wrapper

从文档中我可以看出,这是它的预期用途之一,但是当涉及到包含多态对象的数组的话题时,常见的建议似乎是使用智能指针而不是std::reference_wrapper

我唯一的想法是智能指针可能能够更整洁地处理对象的生命周期?

TL:DR;为什么在创建多态对象数组时,像std::unique_ptr 这样的智能指针似乎比std::reference_wrapper 更受欢迎?

【问题讨论】:

  • 如果对象在其他地方拥有,reference_wrapper 很好。但是,如果此容器拥有对象并控制其生命周期,则必须使用智能指针,例如 unique_ptr - reference_wrapper 不提供任何所有权语义。
  • unique_ptr 管理所拥有对象的生命周期,reference_wrapper 存储指向该对象的指针。使用 unique_ptrs 保证对象的释放!
  • 引用只是一个引用,仍然需要有人照顾对象的生命周期。使用智能指针,您可以立即获得所需的一切
  • 其实你的问题已经有了答案。唯一要补充的是,它不是关于“更整洁”,而是引用根本不处理生命周期
  • std::reference_wrapper 作为容器元素类型与常规哑指针相比没有任何优势,除了引用包装不能为空。

标签: c++ arrays reference polymorphism smart-pointers


【解决方案1】:

简单来说:

  • unique_ptr 是对象的所有者。它管理拥有对象的生命周期

  • reference_wrapper 包装了指向内存中对象的指针。它管理被包装对象的生命周期

您应该创建一个unique_ptr(或shared_ptr)数组,以保证在不再需要对象时释放它。

【讨论】:

  • 在处理堆中的对象时尽量使用智能指针。当我需要处理多态性时,我会像你一样使用指针,但我总是尽量不使用指针!
【解决方案2】:

如果你有足够的动力,你可以写一个poly_any<Base>类型。

poly_any<Base> 是一个any,仅限于存储派生自Base 的对象,并提供一个.base() 方法,该方法将Base& 返回到底层对象。

一个非常不完整的草图:

template<class Base>
struct poly_any:private std::any
{
  using std::any::reset;
  using std::any::has_value;
  using std::any::type;

  poly_any( poly_any const& ) = default;
  poly_any& operator=( poly_any const& ) = default;

  Base& base() { return get_base(*this); }
  Base const& base() const { return const_cast<Base const&>(get_base(const_cast<poly_any&>(*this))); }

  template< class ValueType,
    std::enable_if_t< /* todo */, bool > =true
  >
  poly_any( ValueType&& value ); // todo

  // TODO: sfinae on ValueType?
  template< class ValueType, class... Args >
  explicit poly_any( std::in_place_type_t<ValueType>, Args&&... args );  // todo

  // TODO: sfinae on ValueType?
  template< class ValueType, class U, class... Args >
  explicit poly_any( std::in_place_type_t<ValueType>, std::initializer_list<U> il,
          Args&&... args ); // todo

  void swap( poly_any& other ) {
    static_cast<std::any&>(*this).swap(other);
    std::swap( get_base, other.get_base );
  }

  poly_any( poly_any&& o ); // todo
  poly_any& operator=( poly_any&& o ); // todo

  template<class ValueType, class...Ts>
  std::decay_t<ValueType>& emplace( Ts&&... ); // todo
  template<class ValueType, class U, class...Ts>
  std::decay_t<ValueType>& emplace( std::initializer_list<U>, Ts&&... ); // todo
private:
  using to_base = Base&(*)(std::any&);
  to_base get_base = 0;
};

然后你只需要拦截将东西放入poly_any&lt;Base&gt;的所有方法并存储一个get_base函数指针:

template<class Base, class Derived>
auto any_to_base = +[](std::any& in)->Base& {
  return std::any_cast<Derived&>(in);
};

完成此操作后,您可以创建一个std::vector&lt;poly_any&lt;Base&gt;&gt;,它是一个由Base 多态继承的值类型的向量。

注意std::any 通常使用小缓冲区优化在内部存储小对象,而在堆上存储较大的对象。但这是一个实现细节。

【讨论】:

    【解决方案3】:

    基本上,reference_wrapper 是一个可变引用:和引用一样,它不能为空;但就像指针一样,您可以在其生命周期内为其赋值以指向另一个对象。

    但是,像指针和引用一样,reference_wrapper 不管理对象的生命周期。这就是我们使用vector&lt;uniq_ptr&lt;&gt;&gt;vector&lt;shared_ptr&lt;&gt;&gt; 的目的:确保引用的对象被正确释放。

    从性能的角度来看,vector&lt;reference_wrapper&lt;T&gt;&gt; 应该与vector&lt;T*&gt; 一样快速且内存高效。但是这两个指针/引用都可能变得悬空,因为它们没有管理对象的生命周期。

    【讨论】:

      【解决方案4】:

      让我们来试试吧:

      #include <iostream>
      #include <vector>
      #include <memory>
      #include <functional>
      
      class Base {
      public:
         Base() {
           std::cout << "Base::Base()" << std::endl;
         }
      
         virtual ~Base() {
           std::cout << "Base::~Base()" << std::endl;
         }
      };
      
      class Derived: public Base {
      public:
         Derived() {
           std::cout << "Derived::Derived()" << std::endl;
         }
      
         virtual ~Derived() {
           std::cout << "Derived::~Derived()" << std::endl;
         }
      };
      
      typedef std::vector<std::reference_wrapper<Base> > vector_ref;
      typedef std::vector<std::shared_ptr<Base> > vector_shared;
      typedef std::vector<std::unique_ptr<Base> > vector_unique;
      
      void fill_ref(vector_ref &v) {
          Derived d;
          v.push_back(d);
      }
      
      void fill_shared(vector_shared &v) {
          std::shared_ptr<Derived> d=std::make_shared<Derived>();
          v.push_back(d);
      }
      
      void fill_unique(vector_unique &v) {
          std::unique_ptr<Derived> d(new Derived());
          v.push_back(std::move(d));
      }
      
      int main(int argc,char **argv) {
      
         for(int i=1;i<argc;i++) {
            if(std::string(argv[i])=="ref") {
          std::cout << "vector" << std::endl;
          vector_ref v;
              fill_ref(v);
          std::cout << "~vector" << std::endl;
            } else if (std::string(argv[i])=="shared") {
          std::cout << "vector" << std::endl;
          vector_shared v;
          fill_shared(v);
          std::cout << "~vector" << std::endl;
            } else if (std::string(argv[i])=="unique") {
          std::cout << "vector" << std::endl;
          vector_unique v;
          fill_unique(v); 
          std::cout << "~vector" << std::endl;
            }
         }
      }
      

      使用共享参数运行:

      vector
      Base::Base()
      Derived::Derived()
      ~vector
      Derived::~Derived()
      Base::~Base()
      

      使用唯一参数运行

      vector
      Base::Base()
      Derived::Derived()
      ~vector
      Derived::~Derived()
      Base::~Base()
      

      使用参数 ref 运行

      vector
      Base::Base()
      Derived::Derived()
      Derived::~Derived()
      Base::~Base()
      ~vector
      

      解释:

      • 共享:内存由代码的不同部分共享。在示例中,Derived 对象首先由函数 fill_shared() 中的 d 局部变量和向量拥有。当代码退出时,函数对象的作用域仍然归向量所有,只有当向量最终离开时,对象才会被删除
      • unique:内存归 unique_ptr 所有。在示例中,Derived 对象首先归 d 本地变量所有。但是它必须移动到向量中,转移所有权。和以前一样,当唯一的所有者离开时,对象会被删除。
      • ref:没有拥有语义。该对象被创建为fill_ref() 函数的局部变量,并且可以将对该对象的引用添加到向量中。但是,向量不拥有内存,当代码超出 fill_ref() 函数时,对象消失,向量指向未分配的内存。

      【讨论】:

        猜你喜欢
        • 2020-11-18
        • 2014-08-14
        • 2010-11-11
        • 1970-01-01
        • 2022-01-02
        • 1970-01-01
        • 2020-07-01
        • 2023-03-16
        • 1970-01-01
        相关资源
        最近更新 更多