指向抽象类型的指针数组

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.

Live demo.

通过工厂

如果调用者真的不想处理任何类型的指针,另一种选择是传递 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);

Live demo.

您可以让您的 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 来实现额外的基础成员。在许多情况下,这可能就足够了。