我有一个类使用一组基类指针来派生对象,所以我需要有自己的析构函数来删除向量的元素和自定义副本和赋值函数。我不完全确定实现下面这样的结构的首选方法,并为它编写正确的副本和赋值构造函数和析构函数。我可以请你指导我吗?我已经阅读了很多并搜索过,但我仍然不确定。
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 );
}
}
}
答案 0 :(得分:1)
你在析构函数中的循环在实践中很好,并且是 通常的解决方形式上,它是未定义的行为,因为你 在向量中保留对象(指向已删除对象的指针) 哪些不可复制,但在实践中:矢量不会复制 除非你把它调整到更大的东西,或插入或 擦除它。如果你真的想避免未定义 行为:
for ( auto current = vect.begin(); current != vect.end(); ++ current ) {
Base* tmp = *it;
*it = nullptr;
delete tmp;
}
但这是一个我可能不会烦恼的案例(和 我倾向于对未定义的行为比大多数人更敏感。
答案 1 :(得分:1)
首先,您是否真的需要复制和分配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;
}
答案 2 :(得分:1)
如果你有一个任何指针类型的向量,向量对指针后面的类型一无所知,也不关心:向量对指针仅进行操作,将它们复制到新的位置,如果需要,但从来没有触摸对象本身。因此,你有责任自己销毁这些物品。
正如James Kanze所指出的那样,处理无效指针时存在未定义行为的轻微危险。但是,由于在删除它所拥有的对象后没有以任何方式使用vector
,因此在您给出的代码中不会调用未定义的行为(向量将不需要重新分配其内存,因此它不会需要分配无效的指针,并且指针的破坏是noop)。因此,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());
}
}