多态对象数组

时间:2018-09-07 12:32:27

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

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

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

从我的文档中可以看出,这是它的预期用途之一,但是当包含多态对象的数组的主题出现时,常见的建议似乎是使用智能指针,而不是{{1} }。

我唯一的想法是,智能指针可能能够稍微整洁地处理对象的寿命?

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

4 个答案:

答案 0 :(得分:14)

非常简单的说法:

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

  • reference_wrapper将指针包装到内存中的对象。它不会管理包装对象的生存期

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

答案 1 :(得分:3)

如果有足够的动力,可以编写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<Base>的所有方法并存储get_base函数指针:

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

完成此操作后,您可以创建一个std::vector<poly_any<Base>>,它是值类型的向量,这些值类型是Base的多态后代。

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

答案 2 :(得分:1)

基本上,reference_wrapper是可变的引用:与引用一样,它不能为null。但是就像指针一样,您可以在其生命周期内分配它以指向另一个对象。

但是,与指针和引用一样, reference_wrapper不能管理对象的生存期。这就是我们使用vector<uniq_ptr<>>vector<shared_ptr<>>的目的:为了确保正确处理引用的对象。

从性能的角度来看,vector<reference_wrapper<T>>应该和vector<T*>一样快且内存效率更高。但是这两个指针/引用都可能悬而未决,因为它们没有管理对象的生存期。

答案 3 :(得分:1)

让我们尝试一下实验:

#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对象首先由函数d中的fill_shared()局部变量和向量所拥有。当代码退出时,功能对象的范围仍归向量所有,只有当向量最终消失时,对象才会被删除
  • 唯一:内存由unique_ptr拥有。在示例中,Derived对象首先由d本地var拥有。但是,必须将其移动到向量中,以转移所有权。与以前一样,当唯一的所有者离开时,该对象将被删除。
  • 参考:没有所属的语义。对象创建为fill_ref()函数的局部变量,并且可以将对对象的引用添加到向量中。但是,向量不拥有内存,并且当代码退出fill_ref()函数时,对象消失了,向量指向未分配的内存。