C++ 中类的复制构造函数

Copy constructor for the class in C++

提问人:Pravej Khan 提问时间:8/4/2022 最后编辑:Mark RotteveelPravej Khan 更新时间:8/13/2022 访问量:168

问:

我想为该类编写一个复制构造函数。Plane

class Widget{ };
class Button: public Widget{};
class Label: public Widget {};
class Plane {
   vector<Widget*>vec;
public:
   void Add (Widget* w) {
      vec.push_back(w);
   }
// need to implement copy constructor for this class???
//Plane (const Plane &obj) ???
~Plane() {
   for (auto w: vec) {
      delete w;
     }
  }
};

int main () {
   Plane p1;
   p1.Add(new Button);
   p1.Add(new Label);
   Plane p2(p1);
   return 0;
}

需要您的帮助来编写 的复制构造函数。我还需要更改某些内容吗?我想要一个深拷贝,这就是为什么这里需要一个复制构造函数。我尝试自己编写复制构造函数,但失败了。class PlaneWidget class

C++ 复制构造函数

评论

1赞 Mikel F 8/4/2022
我投票结束这个问题,因为 Stack Overflow 不是来为人们编写代码的,而是用他们已经尝试过的代码来回答问题或解决问题。
4赞 273K 8/4/2022
也许你需要.virtual Widget* Widget::Clone();
1赞 Mikel F 8/4/2022
如果您添加代码来演示您已经尝试过的内容,或者可以显示您尝试中的错误消息,我将撤销我的接近投票。举个例子,说明您是如何尝试解决问题的。
2赞 freakish 8/4/2022
使用此析构函数,您已经不安全了:您拥有 Widget 指针的向量,但由于析构函数杀死了它们,因此无法共享这些指针。正确的方法是使用 unique_ptr<Widget> 或 shared_ptr<Widget> 的向量,在这种情况下,您不需要析构函数,并且复制构造函数要么是不允许的,要么是自动的。或者,您需要知道如何(虚拟)复制 Widget 实例。Plane
2赞 n. m. could be an AI 8/5/2022
Widget层次结构会遗漏虚拟析构函数和其他虚拟函数。没有这些,你就无法工作。vector<Widget*>

答:

2赞 Giogre 8/7/2022 #1

我不知道你为什么决定使用原始指针而不是 s。unique_ptr

unique_ptr具有 RAII 功能,这意味着当超出范围时,它们会自动销毁指向的资源以及指针。

相反,使用原始指针时,您需要手动使用它们。此外,如果将它们用作自定义类中的数据成员,则还需要定义自己的&构造函数和赋值运算符。deletecopymove

一种方法(我听说在Java中是臭名昭著的,在C++中更不用说了)将允许您根据代码的需要执行深度复制。clone()vector<Widget*>

如果按如下方式修改,该程序应该可以工作:

#include <iostream>
#include <vector>
#include <utility>
#include <stdexcept>

using std::cout;
using std::vector;
using std::move;
using std::exception;
using std::logic_error;

class Widget
{
public:
    Widget() {cout << "created Base Widget\n";}
    Widget(const Widget& original) noexcept {
        cout << "copied Base Widget\n";
    }

    virtual ~Widget(){} // need virtual destructor with class hierarchy

    virtual Widget* clone() const { // makes deep copies of pointers
        if constexpr(noexcept(Widget(*this))) {
            try {
                cout << "cloned Widget\n";
                return new Widget(*this);
            } 
            catch(exception& e) {
                throw runtime_error("error occurred while cloning Widget");
            }
        } else 
            throw logic_error("Widget: Faulty constructor");           
    }
};

class Button: public Widget{
public:
    Button(): Widget() { cout << "created Button\n"; }
    Button(const Button& original) noexcept: Widget(original){ 
        cout << "copied Button\n";
    }
    
    virtual Button* clone() const {
        if constexpr(noexcept(Button(*this))) {
            try {
                cout << "cloned Button\n";
                return new Button(*this);
            } 
            catch(exception& e) {
                throw runtime_error("error occurred while cloning Button");
            }
        } else 
            throw logic_error("Button: Faulty constructor");           
    }
    
    virtual ~Button(){}
};

class Label: public Widget {
public:
    Label(): Widget() { cout << "created Label\n"; }
    Label(const Label& original) noexcept: Widget(original){
        cout << "copied Label\n";
    }
    
    virtual Label* clone() const {
        if constexpr(noexcept(Label(*this))) {
            try {
                cout << "cloned Label\n";
                return new Label(*this);
            } 
            catch(exception& e) {
                throw runtime_error("error occurred while cloning Label");
            }
        } else 
            throw logic_error("Label: Faulty constructor");
    }
    
    virtual ~Label(){}
};

class Plane { // destructor is needed so copy&move semantics are also needed
              // (rule of 5)
    vector<Widget*>vec;
public:
    Plane(): vec(0) {}

    Plane(const Plane& original)  // performs deep copy
    {   
        vec.resize(original.vec.size());
        for (size_t i = 0; i != original.vec.size(); ++i) 
            vec[i] = original.vec[i]->clone();
    }

    Plane& operator=(const Plane& original)
    {   
        if (this != &original) {
            vec.resize(original.vec.size());
            for (size_t i = 0; i != original.vec.size(); ++i) 
                vec[i] = original.vec[i]->clone();
        }

        return *this;
    }

    ~Plane() {
        for (auto w: vec) {
            delete w;
            cout << "deleted Widget\n";
        }
    }

    // for completeness also add move semantics
    Plane(Plane&& original) noexcept
    {
        vec = move(original.vec);
        cout << "moved Plane\n";
    }

    Plane& operator=(Plane&& original) noexcept
    {
        if (this != &original)
            vec.swap(original.vec);

        return *this;
    }

    void Add (Widget* w) {
        vec.push_back(w);
        cout << "added Widget*\n";
    }
};

int main () {
   Plane p1;
   p1.Add(new Button);
   p1.Add(new Label);
   Plane p2(p1);
   
   return 0;
}

结果符合预期:四个 s 是 d(两个 in 和 2 个 inWidgetdeletep1p2)

created Base Widget
created Button
added Widget*
created Base Widget
created Label
added Widget*
cloned Button
copied Base Widget
copied Button
cloned Label
copied Base Widget
copied Label
deleted Widget
deleted Widget
deleted Widget
deleted Widget

评论

2赞 user17732522 8/13/2022
否则,这也不是例外安全的。如果其中一个克隆元素的分配/构造发生,则已经克隆的元素将被泄露。std::unique_ptr
2赞 user17732522 8/13/2022
也是一个小问题,但并不完全正确。如果倒计时,则使用 可以根据 推断出正确的类型,但这始终可以推断出哪个类型可能太小而无法容纳大小,在这种情况下,它将溢出未定义的行为。auto i = 0autosizeautoint
1赞 Giogre 8/13/2022
@user17732522 谢谢。添加了构造,并将该循环索引更改为 。try ... catchclone()autosize_t
3赞 user17732522 8/13/2022
不幸的是,/没有做任何事情来真正解决问题。如果存在例外,则副本将不完整。正确添加异常安全需要在循环调用中捕获异常,然后遍历索引小于当前的所有元素以调用它们,然后重新抛出异常。因为这相当复杂,所以不应该手动完成。如果与 和 一起使用,而不是从一开始就使用,那么这将自动正常工作。trycatchforcloneideletestd::vectorstd::unique_ptrstd::make_uniquenew
1赞 user17732522 8/13/2022
如果溢出,则不会引发异常。循环中的溢出增量此时将具有未定义的行为。但是您使用 正确地解决了该问题。相关分配总是有两个版本,一个是另一个不是。没有放置参数的表达式将始终使用抛出参数。但不仅分配可以抛出。构造函数也可以(不是在这里,而是在典型情况下)也可以抛出。original.vec.size()intsize_toperator newnoexceptnewstd::nothrow