C++ 复制构造函数清除 向量<基*> 派生*

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

本文关键字:复制 gt 派生 构造函数 向量 C++ lt 清除      更新时间:2023-10-16

我有一个类,它使用一类指向派生对象的基指针,所以我需要有自己的构造函数来删除向量的元素以及自定义的复制和赋值函数。我不完全确定实现下面这样的结构并为其编写正确的复制和赋值构造函数和析构函数的首选方法。我可以请你指导我吗?我读了很多书,搜索了很多,但我还是不确定。

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_that添加一个虚拟函数是否有意义。否则,如果heimer的方法不允许未来的新派生类,那么虚拟方法会。)

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());
    }
}