繁体   English   中英

C ++复制构造函数在向量上清除 <Base*> 衍生*

[英]C++ copy constructor clear up on vector<Base*> of Derived*

我有一个使用基类指针指向派生对象的类,因此我需要有一个自己的解释器来删除向量的元素以及自定义的复制和赋值功能。 我不完全确定实现以下结构并为其编写正确的副本,赋值构造函数和析构函数的首选方法。 我可以请你指导我吗? 我已经阅读了很多书并进行了搜索,但是我仍然不确定。

class Base 
{
   public:
   Base();
   ~virtual Base();

   int a;
   int type; // Derived1 or Derived2
   std::string b;
   ...
}

class Derived1 : public Base 
{
public:
    Derived1();
    Derived1(const Base* a);
    virtual ~Derived1();
}

class Derived1 
{
    Derived1::Derived1(const Base *a) : Base(a) 
    {

    }
}

class Derived2 : public Base
{
public:
    Derived2();
    Derived2(const Base* a);
    virtual ~Derived2();

    std::string d1;
    std::string d2;
    int d3;
}

class Derived2 
{
    Derived2::Derived2(const Base *a) : Base(a) {
        this->d1 = ((Derived2*)a)->d1;
        this->d2 = ((Derived2*)a)->d2;
        this->d3 = ((Derived2*)a)->d3;
    }
}


class A 
{
public:
    A();
    ~A();
    A(const A& a);
    A& operator = (const A& a);

    std::string someString;
    std::vector<Base*> vect;
}

A::~A() {
    std::vector<Base*>::iterator it = vect.begin();
    while (it != vect.end()) {
        delete (*it);
        it++;
}

A::A(const A &a)
{
    someString = a.someString;
    for(std::size_t i = 0; i < a.vect.size(); ++i {
        someString = a.someString;
        Base* base = a.vect.at(i);
        if(base->type == base::TypeD1) {
            base = new Derived1( a.vect.at(i) );
            vect.push_back( base );
        }
        else {
            base = new Derived2( a.vect.at(i) );
            vect.push_back( base );
        }
    }
}

析构函数中的循环实际上是很好的方法,并且是通常的解决方案。 形式上,这是未定义的行为,因为您将无法复制的矢量对象留在了矢量中(指向已删除对象的指针),但实际上:除非将矢量调整为更大的尺寸或插入或擦除,否则矢量不会复制它们。在上面。 如果您确实想避免未定义的行为:

for ( auto current = vect.begin(); current != vect.end(); ++ current ) {
    Base* tmp = *it;
    *it = nullptr;
    delete tmp;
}

但这是我可能不会打扰的一种情况(而且我对未定义行为的敏感度比大多数人还高)。

首先,您实际上是否需要复制和分配A型对象? 如果否,则简单的解决方案是:

class A
{
public:
    A();
    ~A();
    A(const A&) = delete;
    A(A&&) = default;
    A& operator=(const A&) = delete;
    A& operator=(A&&) = default;

    // ...
};

如果是,那么您需要某种多态的方式来复制矢量的元素。 (只要有, if (b->type == Base::TypeD1) { do_this(); } else { do_that(); } ,请停下来思考是否为do_this添加虚拟函数是否do_this / do_that -if- do_that方法不允许将来的新派生类;虚拟方法允许。)

class Base
{
public:
    // ...
    virtual Base* clone() const = 0;
    // ...
};

class Derived1 : public Base
{
public:
    virtual Derived1* clone() const;
};

Derived1* Derived1::clone() const {
    return new Derived1(*this);
}

A的复制分配运算符将需要像销毁器一样销毁lhs的旧内容,然后像复制构造函数一样复制新内容。 因此,让我们将这两个操作放在私有函数中:

class A
{
public:
    A();
    ~A();
    A(const A&);
    A(A&&) = default;
    A& operator=(const A&);
    A& operator=(A&&) = default;

    // ...
private:
    void destroy_contents();
    void copy_from(const std::vector<Base*>& v);
};

void A::destroy_contents() {
    std::vector<Base*>::iterator it = vect.begin();
    while (it != vect.end()) {
        delete (*it);
        ++it;
    }
    vect.clear();
}

void A::copy_from(const std::vector<Base*>& v) {
    std::vector<Base*>::const_iterator it = v.begin();
    while (it != v.end()) {
        vect.push_back((*v)->clone());
        ++it;
    }
}

A::~A() { destroy_contents(); }

A::A(const A& a) :
    someString(a.someString),
    vect()
{
    copy_from(a.vect);
}

A& A::operator=(const A& a) {
    if (this != &a) {
        someString = a.someString;
        destroy_contents();
        copy_from(a.vect);
    }
    return *this;
}

如果您具有任何指针类型的向量,则向量对指针后面的类型一无所知,也不会在意:向量仅对指针起作用,如有必要,将其复制到新位置,但永远不要触摸对象本身。 因此,销毁对象本身是您的责任。

正如James Kanze指出的那样,在处理无效指针时,存在未定义行为的轻微危险。 但是,由于vector在删除其持有的对象后未以任何方式使用,因此在给定的代码中不会调用未定义的行为(向量无需重新分配其内存,因此不需要分配无效的内存)指针,而销毁指针则不会发生)。 这样,您的class A的析构函数就可以了。

但是, class A的副本构造函数不必要地复杂,并且有大量的错误源(每当定义新的派生类时,都需要对其进行更新!)。 最好的方法是使用clone()函数:

class Base {
    public:
        //...
        virtual Base* clone() const = 0;    //Returns a new copy of the object. Pure virtual if the Base class is abstract.
};

class Derived1 : public Base {
    public:
        Derived1(const Derived& a);
        virtual Derived* clone() const {
            return new Derived1(*this);
        }
};

如果在基类中将clone()纯为虚拟,则可以保证编译器在忘记在任何派生类中实现时都会抱怨。 这样, class A的副本构造函数就变得微不足道了:

A::A(const A &a) {
    someString = a.someString;
    for(std::size_t i = 0; i < a.vect.size(); ++i {
        vect.push_back(a.vect[i]->clone());
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM