从现有实例中创建新的类实例一次

Create new class instances from existing once?

本文关键字:实例 一次 创建      更新时间:2023-10-16

我得到了一个指向基类foo的指针向量,该基类有几个子类,我想做的是根据它是哪个子类,创建一个相同实例的新类。

我之前已经解决了这个问题,有一个巨大的for循环,它使用typeid来找出它是什么类,但没有更通用的方法来解决它吗?

基本上,像这样的东西就是我想要的:

std::vector<foo*> a;
std::vector<foo*> b;
//Store a couple of classes in a and b
b[0] = new typeid(a[0]).name();

一种方法是使用虚拟clone()方法,该方法返回指向正确类型的对象的指针:

struct base 
{
  virtual base* clone()=0;
  virtual ~base() {}
};
struct foo : public base
{
  virtual base* clone() { return new foo(*this); }
};
struct bar : public base
{
  virtual base* clone() { return new bar(*this); }
};

那么你的代码就是

b[0] = a[0].clone();

在现实生活中的代码中,您会返回一个智能指针,例如std::unique_ptr<base>

无论何时,只要您有一个基于类型的巨大开关,您可能都应该使用虚拟函数。

您应该引入某种虚拟clone()函数:

#include <iostream>
#include <memory>
struct base
{
    virtual ~base() {};
    virtual void do_stuff() = 0;
    // cloning interface
    std::unique_ptr<base> clone() const
    {
        return std::unique_ptr<base>(do_clone());
    }
private:
    // actual clone implementation; uses a raw pointer to support covariance
    virtual base* do_clone() const = 0;
};
struct derived_A : base
{
    void do_stuff() override { std::cout << "stuff in derived_A" << std::endl; }
    // purposefully hide the base implementation,
    // since we know we'll be returning a derived_A
    std::unique_ptr<derived_A> clone() const
    {
        return std::unique_ptr<derived_A>(do_clone());
    }
private:
    derived_A* do_clone() const override
    {
        return new derived_A(*this);
    }
};
struct derived_B : base
{
    void do_stuff() override { std::cout << "stuff in derived_B" << std::endl; }
    // purposefully hide the base implementation,
    // since we know we'll be returning a derived_B
    std::unique_ptr<derived_B> clone() const
    {
        return std::unique_ptr<derived_B>(do_clone());
    }
private:
    derived_B* do_clone() const override
    {
        return new derived_B(*this);
    }
};
#include <vector>
int main()
{
    std::vector<std::unique_ptr<base>> v1;
    std::vector<std::unique_ptr<base>> v2;
    std::unique_ptr<base> x(new derived_A);
    v1.push_back(std::move(x));
    std::unique_ptr<base> y(new derived_B);
    v1.push_back(std::move(y));
    v1[0]->do_stuff();
    v1[1]->do_stuff();
    // clone
    v2.push_back(v1[0]->clone());
    v2.push_back(v1[1]->clone());
    v2[0]->do_stuff();
    v2[1]->do_stuff();
}

我们想要返回类型的协方差(如果您持有指向静态类型的derived_A的指针,则克隆它应该会生成derived_A以避免冗余强制转换),这就是为什么克隆接口被拆分为两部分的原因。如果std::unique_ptr<base>std::unique_ptr<derived>协变,则可以在一个指针中完成,但这只是原始指针的情况。

我相信有一种方法可以隐藏重复的样板,这是读者的一个练习。


编辑:事实上,给你;不太难:

#include <memory>
// Note: leaves with a public: access specifier
#define DEFINE_ABSTRACT_CLONEABLE(selfType)         
        DEFINE_CLONEABLE_DETAIL(selfType)           
        private:                                    
            virtual selfType* do_clone() const = 0; 
                                                    
        public:
// Note: leaves with a public: access specifier
#define DEFINE_CLONEABLE(selfType)              
        DEFINE_CLONEABLE_DETAIL(selfType)       
        private:                                
            selfType* do_clone() const override 
            {                                   
                return new selfType(*this);     
            }                                   
                                                
        public:
#define DEFINE_CLONEABLE_DETAIL(selfType)                                   
        public:                                                             
            std::unique_ptr<selfType> clone() const                         
            {                                                               
                static_assert(std::is_same<selfType,                        
                                          std::decay<decltype(*this)>::type 
                                          >::value,                         
                             "Must specify current class name.");           
                                                                            
                return std::unique_ptr<selfType>(do_clone());               
            }                                                               

和测试(注意较小的尺寸):

#include <iostream>
#include "cloneable.hpp" // or whatever 
struct base
{
    // readable error: DEFINE_ABSTRACT_CLONEABLE(int);
    DEFINE_ABSTRACT_CLONEABLE(base);
    virtual ~base() {};
    virtual void do_stuff() = 0;
};
struct derived_A : base
{
    DEFINE_CLONEABLE(derived_A);
    void do_stuff() override { std::cout << "stuff in derived_A" << std::endl; }
};
struct derived_B : base
{
    // error: DEFINE_CLONEABLE(derived_B);
    DEFINE_ABSTRACT_CLONEABLE(derived_B);
    void do_stuff() override { std::cout << "stuff in derived_B" << std::endl; }
    virtual void do_thing() = 0; // abstract again
};
struct derived_AA : derived_A
{
    DEFINE_CLONEABLE(derived_AA);
    void do_stuff() override { std::cout << "stuff in derived_AA" << std::endl; }
};
struct derived_BB : derived_B
{
    DEFINE_CLONEABLE(derived_BB);
    void do_stuff() override { std::cout << "doing stuff in derived_BB" << std::endl; }
    void do_thing() override { std::cout << "doing thing" << std::endl; }
};
int main()
{
    std::unique_ptr<derived_AA> x(new derived_AA());
    x->do_stuff();
    auto xx = x->clone();
    xx->do_stuff();
    std::unique_ptr<derived_A> xxx = xx->clone();
    xxx->do_stuff();
    std::unique_ptr<base> xxxx = xxx->clone();
    xxxx->do_stuff();
    xxxx->clone()->do_stuff();
    std::unique_ptr<derived_BB> y(new derived_BB());
    y->do_stuff();
    y->do_thing();
    auto yy = y->clone();
    yy->do_stuff();
    yy->do_thing();
    std::unique_ptr<derived_B> yyy = yy->clone();
    yyy->do_stuff();
    yyy->do_thing();
    std::unique_ptr<base> yyyy = yyy->clone();
    yyyy->do_stuff();
    // error, lost derived information: yyyy->do_thing(); 
    yyyy->clone()->do_stuff();
}

另一个改进是使do_clone的每个新声明都是纯虚拟的,以强制进一步的派生类实现它,但这个留给读者。

实现这一点的更好方法是使用dynamic_cast。您可以尝试dynamic_casting,然后调用相应的复制构造函数。话虽如此,我还是会选择clone解决方案,或者尝试重新设计解决问题的方法。

例如,

child *c = dynamic_cast<child*>(base);
if(c != NULL) {
    return new child(c);
}