指向抽象类型的指针数组
Arrays of Pointers to Abstract Types
我一直在尝试抽象类型。
下面的代码给了我想要的效果。
class base{
public:
virtual void do_stuff() = 0;
};
class derived: public base{
public:
void do_stuff(){/*stuff*/}
};
class manager{
vector<shared_ptr<base>> ptrs;
public:
void add(base* ptr){
ptrs.emplace_back(ptr);
}
};
manager foo;
foo.add(new derived());
很好,但很尴尬,因为用户不仅要处理指针,而且还必须使用 new
而无需调用 delete
。我的问题是,是否有一种方法可以让 manager
的用户不必处理指针或 new
.
foo.add(derived()); //example
我的尝试最终实现为:
class manager{
vector<shared_ptr<base>> ptrs;
public:
void add(base& ref){
ptrs.emplace_back(&ref);
}
};
但是,编译器说 no known conversion from 'derived' to 'base&'
。我不知道如何使对 base
的引用与对 derived
的引用兼容。我该如何解决这个问题?
及格unique_ptr
您的 add
函数取得了该对象的所有权。传递所有权的一种安全方法是传递 unique_ptr
.
使用 unique_ptr
相当灵活,因为您可以从 unique_ptr
构建 shared_ptr
,或者如果您以后改变主意,您可以存储 unique_ptr
直接地。
class manager{
vector<shared_ptr<base>> ptrs;
public:
void add(std::unique_ptr<base> ptr){
ptrs.emplace_back(std::move(ptr));
}
};
manager foo;
foo.add(std::make_unique<derived>());
使用临时 std::unique_ptr
可以避免拥有非异常安全的原始指针。通过使用 make_unique
你可以避免写 new
.
通过工厂
如果调用者真的不想处理任何类型的指针,另一种选择是传递 add
函数用来构造对象的某种工厂。工厂可以简单地是 derived
class 本身的 static
create
函数:
using Factory = std::function<std::unique_ptr<base>()>;
class manager{
std::vector<std::shared_ptr<base>> ptrs;
public:
void addUsing(const Factory& factory){
ptrs.emplace_back(factory());
}
};
class derived : public base {
public:
...
static std::unique_ptr<derived> create() {
return std::make_unique<derived>();
}
};
manager foo;
foo.addUsing(derived::create);
您可以让您的 add()
函数传递要在类型 T
的构造中使用的参数,其中 T
被指定为子类的类型。
template <typename T, typename... TArgs>
void add(TArgs&&... args)
{
ptrs.emplace_back(std::make_shared<T>(std::forward<TArgs>(args)...));
}
然后可以调用如下:
bm.add<derived_a>( "hello" ); // derived_a constructor takes a string
bm.add<derived_b>( 42 ); // derived_b constructor takes an int
完整示例
#include <string>
#include <vector>
#include <memory>
class base
{
public:
virtual void f() = 0;
};
class derived_a : public base
{
public:
derived_a( std::string const& s ) : s_{ s } {}
void f() override { std::cout << "derived_a::string = " << s_ << '\n'; }
private:
std::string s_;
};
class derived_b : public base
{
public:
derived_b( int i ) : i_{ i } {}
void f() override { std::cout << "derived_b::int = " << i_ << '\n'; }
private:
int i_;
};
class base_manager
{
public:
template <typename T, typename... TArgs>
void add( TArgs&&... args )
{
ptrs.emplace_back( std::make_shared<T>( std::forward<TArgs>( args )... ) );
}
void print() { for ( auto& d : ptrs ) d->f(); }
private:
std::vector<std::shared_ptr<base>> ptrs;
};
int main()
{
base_manager bm;
bm.add<derived_a>( "hello" );
bm.add<derived_b>( 42 );
bm.print();
}
您不能将临时(r 值)传递给非常量引用。您还尝试获取该临时对象的地址,这最终会产生悬空指针和未定义的行为。
假设您要将未知运行时类型的对象传递给管理器:
您可以做的一件事是使用某种多态复制机制(如虚拟克隆方法)并在堆上制作对象的内部副本(它必须是多态的,以避免对象切片)。
class base {
public:
virtual void do_stuff() = 0;
virtual shared_ptr<base> clone() const = 0;
virtual ~base()=default;
};
class derived : public base {
int data;
public:
derived() :data(0) {};
derived(const derived& other) :data(other.data)
{};
virtual shared_ptr<base> clone() const override {
return make_shared<derived>(*this);
};
void do_stuff() {/*stuff*/ }
};
class manager {
vector<shared_ptr<base>> ptrs;
public:
void add(const base& obj) {
ptrs.emplace_back(obj.clone());
}
};
int main() {
manager foo;
foo.add(derived());
}
没有 clone
,它看起来像这样:
void add(const base& obj) {
if (typeid(obj)== typeid(derived) ){
ptrs.emplace_back(make_shared<derived>(static_cast<const derived&>(obj)));
}
else if (typeid(obj) == typeid(derived2)) {
...
}
您最初的问题似乎与 user/caller 创建一个指针并传递它并且从不删除它这一事实有关。我在下面的例子中,只是简单地向用户明确表示他可以把它交给他而忘记它。换句话说,要求用户传递 shared_ptr...
#include <stdlib.h>
#include <vector>
#include <memory>
using namespace std;
class base{
public:
virtual void do_stuff() = 0;
};
class derived : public base{
public:
void do_stuff(){/*stuff*/ }
};
class manager{
vector<shared_ptr<base>> ptrs;
public:
void add(shared_ptr<base> ptr){
ptrs.emplace_back(ptr);
}
};
int main()
{
manager foo;
shared_ptr<derived> bp(new derived()); //require the user supply a smart pointer
foo.add(bp);
return 0;
}
这比其他帖子更简单,可能没有前瞻性思维,但它不需要派生的 class 来实现额外的基础成员。在许多情况下,这可能就足够了。
我一直在尝试抽象类型。 下面的代码给了我想要的效果。
class base{
public:
virtual void do_stuff() = 0;
};
class derived: public base{
public:
void do_stuff(){/*stuff*/}
};
class manager{
vector<shared_ptr<base>> ptrs;
public:
void add(base* ptr){
ptrs.emplace_back(ptr);
}
};
manager foo;
foo.add(new derived());
很好,但很尴尬,因为用户不仅要处理指针,而且还必须使用 new
而无需调用 delete
。我的问题是,是否有一种方法可以让 manager
的用户不必处理指针或 new
.
foo.add(derived()); //example
我的尝试最终实现为:
class manager{
vector<shared_ptr<base>> ptrs;
public:
void add(base& ref){
ptrs.emplace_back(&ref);
}
};
但是,编译器说 no known conversion from 'derived' to 'base&'
。我不知道如何使对 base
的引用与对 derived
的引用兼容。我该如何解决这个问题?
及格unique_ptr
您的 add
函数取得了该对象的所有权。传递所有权的一种安全方法是传递 unique_ptr
.
使用 unique_ptr
相当灵活,因为您可以从 unique_ptr
构建 shared_ptr
,或者如果您以后改变主意,您可以存储 unique_ptr
直接地。
class manager{
vector<shared_ptr<base>> ptrs;
public:
void add(std::unique_ptr<base> ptr){
ptrs.emplace_back(std::move(ptr));
}
};
manager foo;
foo.add(std::make_unique<derived>());
使用临时 std::unique_ptr
可以避免拥有非异常安全的原始指针。通过使用 make_unique
你可以避免写 new
.
通过工厂
如果调用者真的不想处理任何类型的指针,另一种选择是传递 add
函数用来构造对象的某种工厂。工厂可以简单地是 derived
class 本身的 static
create
函数:
using Factory = std::function<std::unique_ptr<base>()>;
class manager{
std::vector<std::shared_ptr<base>> ptrs;
public:
void addUsing(const Factory& factory){
ptrs.emplace_back(factory());
}
};
class derived : public base {
public:
...
static std::unique_ptr<derived> create() {
return std::make_unique<derived>();
}
};
manager foo;
foo.addUsing(derived::create);
您可以让您的 add()
函数传递要在类型 T
的构造中使用的参数,其中 T
被指定为子类的类型。
template <typename T, typename... TArgs>
void add(TArgs&&... args)
{
ptrs.emplace_back(std::make_shared<T>(std::forward<TArgs>(args)...));
}
然后可以调用如下:
bm.add<derived_a>( "hello" ); // derived_a constructor takes a string
bm.add<derived_b>( 42 ); // derived_b constructor takes an int
完整示例
#include <string>
#include <vector>
#include <memory>
class base
{
public:
virtual void f() = 0;
};
class derived_a : public base
{
public:
derived_a( std::string const& s ) : s_{ s } {}
void f() override { std::cout << "derived_a::string = " << s_ << '\n'; }
private:
std::string s_;
};
class derived_b : public base
{
public:
derived_b( int i ) : i_{ i } {}
void f() override { std::cout << "derived_b::int = " << i_ << '\n'; }
private:
int i_;
};
class base_manager
{
public:
template <typename T, typename... TArgs>
void add( TArgs&&... args )
{
ptrs.emplace_back( std::make_shared<T>( std::forward<TArgs>( args )... ) );
}
void print() { for ( auto& d : ptrs ) d->f(); }
private:
std::vector<std::shared_ptr<base>> ptrs;
};
int main()
{
base_manager bm;
bm.add<derived_a>( "hello" );
bm.add<derived_b>( 42 );
bm.print();
}
您不能将临时(r 值)传递给非常量引用。您还尝试获取该临时对象的地址,这最终会产生悬空指针和未定义的行为。
假设您要将未知运行时类型的对象传递给管理器:
您可以做的一件事是使用某种多态复制机制(如虚拟克隆方法)并在堆上制作对象的内部副本(它必须是多态的,以避免对象切片)。
class base {
public:
virtual void do_stuff() = 0;
virtual shared_ptr<base> clone() const = 0;
virtual ~base()=default;
};
class derived : public base {
int data;
public:
derived() :data(0) {};
derived(const derived& other) :data(other.data)
{};
virtual shared_ptr<base> clone() const override {
return make_shared<derived>(*this);
};
void do_stuff() {/*stuff*/ }
};
class manager {
vector<shared_ptr<base>> ptrs;
public:
void add(const base& obj) {
ptrs.emplace_back(obj.clone());
}
};
int main() {
manager foo;
foo.add(derived());
}
没有 clone
,它看起来像这样:
void add(const base& obj) {
if (typeid(obj)== typeid(derived) ){
ptrs.emplace_back(make_shared<derived>(static_cast<const derived&>(obj)));
}
else if (typeid(obj) == typeid(derived2)) {
...
}
您最初的问题似乎与 user/caller 创建一个指针并传递它并且从不删除它这一事实有关。我在下面的例子中,只是简单地向用户明确表示他可以把它交给他而忘记它。换句话说,要求用户传递 shared_ptr...
#include <stdlib.h>
#include <vector>
#include <memory>
using namespace std;
class base{
public:
virtual void do_stuff() = 0;
};
class derived : public base{
public:
void do_stuff(){/*stuff*/ }
};
class manager{
vector<shared_ptr<base>> ptrs;
public:
void add(shared_ptr<base> ptr){
ptrs.emplace_back(ptr);
}
};
int main()
{
manager foo;
shared_ptr<derived> bp(new derived()); //require the user supply a smart pointer
foo.add(bp);
return 0;
}
这比其他帖子更简单,可能没有前瞻性思维,但它不需要派生的 class 来实现额外的基础成员。在许多情况下,这可能就足够了。