多态对象数组
Array of polymorphic objects
我经常遇到创建多态对象的数组或向量的需要。我通常更喜欢使用引用,而不是智能指针,指向基 class 因为它们往往更简单。
禁止数组和向量包含原始引用,因此我倾向于使用指向基 classes 的智能指针。但是,也可以选择使用 std::reference_wrapper
代替:https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper
从文档中我可以看出,这是它的预期用途之一,但是当包含多态对象的数组主题出现时,常见的建议似乎是使用智能指针而不是 std::reference_wrapper
.
我唯一的想法是智能指针可以更整洁地处理对象的生命周期?
TL:DR;为什么智能指针,例如 std::unique_ptr
在创建多态对象数组时似乎比 std::reference_wrapper
更受欢迎?
简单来说:
unique_ptr
是对象的所有者。它管理拥有对象的生命周期
reference_wrapper
包装指向内存中对象的指针。它 NOT 管理包装对象的生命周期
您应该创建一个 unique_ptr
(或 shared_ptr
)的数组,以保证在不再需要对象时释放该对象。
基本上,reference_wrapper
是一个可变引用:像引用一样,它不能为空;但就像指针一样,您可以在其生命周期内对其进行赋值以指向另一个对象。
但是,与指针和引用一样,reference_wrapper
不管理对象的生命周期。这就是我们使用 vector<uniq_ptr<>>
和 vector<shared_ptr<>>
的目的:确保正确处理引用的对象。
从性能的角度来看,vector<reference_wrapper<T>>
应该与 vector<T*>
一样快且内存效率高。但是这两个 pointers/references 可能会变得悬空,因为它们不管理对象生命周期。
让我们试试这个实验:
#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()
运行 参数引用
vector
Base::Base()
Derived::Derived()
Derived::~Derived()
Base::~Base()
~vector
解释:
- shared:内存由代码的不同部分共享。在示例中,
Derived
对象首先由函数 fill_shared()
中的 d
局部变量和向量所有。当代码退出时,函数对象的作用域仍然属于 vector,只有当 vector 最终离开时,对象才会被删除
- unique:内存归unique_ptr所有。在示例中,
Derived
对象首先由 d
局部变量拥有。但是它必须 移动 到向量中,转移所有权。和以前一样,当唯一的所有者离开时,对象将被删除。
- ref:没有拥有语义。该对象被创建为
fill_ref()
函数的局部变量,并且可以将对该对象的引用添加到 vector 中。但是,向量不拥有内存,当代码退出 fill_ref()
函数时,对象消失,向量指向未分配的内存。
如果你有足够的动力,你可以写一个poly_any<Base>
类型。
A poly_any<Base>
是一个 any
,仅限于存储从 Base
派生的对象,并提供 .base()
方法 returns a 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
通常使用小缓冲区优化在内部存储小对象,在堆上存储大对象。但这是一个实现细节。
我经常遇到创建多态对象的数组或向量的需要。我通常更喜欢使用引用,而不是智能指针,指向基 class 因为它们往往更简单。
禁止数组和向量包含原始引用,因此我倾向于使用指向基 classes 的智能指针。但是,也可以选择使用 std::reference_wrapper
代替:https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper
从文档中我可以看出,这是它的预期用途之一,但是当包含多态对象的数组主题出现时,常见的建议似乎是使用智能指针而不是 std::reference_wrapper
.
我唯一的想法是智能指针可以更整洁地处理对象的生命周期?
TL:DR;为什么智能指针,例如 std::unique_ptr
在创建多态对象数组时似乎比 std::reference_wrapper
更受欢迎?
简单来说:
unique_ptr
是对象的所有者。它管理拥有对象的生命周期reference_wrapper
包装指向内存中对象的指针。它 NOT 管理包装对象的生命周期
您应该创建一个 unique_ptr
(或 shared_ptr
)的数组,以保证在不再需要对象时释放该对象。
基本上,reference_wrapper
是一个可变引用:像引用一样,它不能为空;但就像指针一样,您可以在其生命周期内对其进行赋值以指向另一个对象。
但是,与指针和引用一样,reference_wrapper
不管理对象的生命周期。这就是我们使用 vector<uniq_ptr<>>
和 vector<shared_ptr<>>
的目的:确保正确处理引用的对象。
从性能的角度来看,vector<reference_wrapper<T>>
应该与 vector<T*>
一样快且内存效率高。但是这两个 pointers/references 可能会变得悬空,因为它们不管理对象生命周期。
让我们试试这个实验:
#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()
运行 参数引用
vector
Base::Base()
Derived::Derived()
Derived::~Derived()
Base::~Base()
~vector
解释:
- shared:内存由代码的不同部分共享。在示例中,
Derived
对象首先由函数fill_shared()
中的d
局部变量和向量所有。当代码退出时,函数对象的作用域仍然属于 vector,只有当 vector 最终离开时,对象才会被删除 - unique:内存归unique_ptr所有。在示例中,
Derived
对象首先由d
局部变量拥有。但是它必须 移动 到向量中,转移所有权。和以前一样,当唯一的所有者离开时,对象将被删除。 - ref:没有拥有语义。该对象被创建为
fill_ref()
函数的局部变量,并且可以将对该对象的引用添加到 vector 中。但是,向量不拥有内存,当代码退出fill_ref()
函数时,对象消失,向量指向未分配的内存。
如果你有足够的动力,你可以写一个poly_any<Base>
类型。
A poly_any<Base>
是一个 any
,仅限于存储从 Base
派生的对象,并提供 .base()
方法 returns a 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
通常使用小缓冲区优化在内部存储小对象,在堆上存储大对象。但这是一个实现细节。