C++ 将带有指针的构造函数复制到自己的类

C++ 将带有指针的构造函数复制到自己的类,c++,c++11,c++14,C++,C++11,C++14,假设我们有这样一个类: class C { public: C() {} virtual ~C() noexcept { if (c) { delete c; } } protected: int a; float b; C* c; } 如何正确实现复制和移动构造函数?通常,您只需调用需要复制的对象的复制构造函数,但由于它是同一个类,您将如何正确处理它?这就是智能指针被发明的原因之一。它们是自我管理的,所以您需要做的唯一一件事就是在您需要的时候设置它们,当不再使用时,它们

假设我们有这样一个类:

class C
{
public:
  C() {}
  virtual ~C() noexcept { if (c) { delete c; } }

protected:
  int a;
  float b;
  C* c;
}

如何正确实现复制和移动构造函数?通常,您只需调用需要复制的对象的复制构造函数,但由于它是同一个类,您将如何正确处理它?

这就是智能指针被发明的原因之一。它们是自我管理的,所以您需要做的唯一一件事就是在您需要的时候设置它们,当不再使用时,它们负责释放内存

#include <memory>

class C
{
public:
  C() {}

protected:
  int a;
  float b;
  std::shared_ptr<C> c;
}
#包括
C类
{
公众:
C(){}
受保护的:
INTA;
浮球b;
std::共享的ptr c;
}
附言:我没有问,但是你需要一个指向自我的指针的情况并不常见;你确定你的情况没有解决办法吗

通常,您只需调用需要复制的对象的复制构造函数,但由于它是同一个类,您将如何正确处理它

以与调用同一类的析构函数相同的递归方式。由于析构函数承担指向对象的所有权,因此必须进行深度复制

移动很简单,只需清除指针,这样当从对象中移动的对象被销毁时,指针就不会被删除。就像任何其他拥有的原始指针一样

C(const C& other): a(other.a), b(other.b), c(other.c) {
    if(other.c)
        this->c = new C(*other.c);
}
C(C&& other): a(other.a), b(other.b), c(other.c) {
    other.c = nullptr;
}
请记住,堆栈大小将限制数据结构的递归。如果将内存管理留在类之外,那么可以使用循环而不是递归来迭代链接对象

如何正确实现复制和移动构造函数

移动很容易:将受害者的指针复制到目标,然后将受害者的指针设为空,以表明它不再拥有任何东西。当然,还要复制其他值

对于复制,您需要选择所需的语义:唯一所有权(在这种情况下不允许复制)、共享所有权(在这种情况下增加引用计数,并更改析构函数以减少引用计数)、深度复制(在这种情况下分配新对象,复制旧对象)或其他

通常,您只需调用需要复制的对象的复制构造函数,但由于它是同一个类,您将如何正确处理它


这不一定是个问题。如果指针链在某个地方结束,您将递归地复制该链上的所有内容;尽管编写循环以避免不确定递归可能更好。如果没有结束,那么它必须是循环的,因此您需要检查是否返回到开始的对象。

好的,问题的目的还不清楚,编写复制和移动操作符的方式将取决于您想要的行为:

  • 是否要在移动时将从属C的所有权移动到新类

  • 如果是副本,是否要复制下级C?(深拷贝)

  • 复制时,目标C是否应与源C共享从属C

我们可以猜测并假设答案是:是,是,否:

class C
{
public:

    // custom destructor == rule of 5

    C() {}

    C(C&& r) noexcept
    : _child { r._child }
    {
        r._child = nullptr;
    }

    C& operator=(C&& r) noexcept {
        swap(r);
        return *this;
    }

    C(const C& r)
    : _child { r._child ? r._child->clone() : nullptr }
    {
    }

    C& operator=(const C& r) {
        C tmp { r };
        swap(tmp);
        return *this;
    }

    // test for null is not necessary
    virtual ~C() noexcept { delete _child; }

public:
    void swap(C& r) noexcept {
        using std::swap;
        swap(_child, r._child);
    }

    // in case C is a polymorphic base class
    virtual C* clone() const {
        return new C { *this };
    }

private:
    C* _child = nullptr;
};

c
在哪里初始化?为什么有
c
而有
this
?无论如何,两个操作符都应该将
c
设置为指向新对象。投票关闭,因为不清楚。我可以回答,例如一个单链表,这可能是,但它可能是无数的其他东西。如果你要处理原始指针和内存管理,我建议你使用它,这取决于新的C实例应该指向哪个C实例。你需要告诉我们更多关于你试图用这种结构解决的问题。智能指针对于链表来说是非常不合适的,这可能是(而且看起来很可能是)。特别是当列表可能是循环的时候。我认为这是一个糟糕的建议。你是对的,这就是我为什么写PS的原因。OP没有说明任何关于应用程序的信息,因此没有进一步的信息,这是一个完全有效的解决方案。我不知道我对投票的感觉如何,因为它是如何被使用的。刀子可以用来杀人,尽管剪刀可能比刀子更好,但刀子仍然是切割纸张等物品的完美选择。对于无保留建议,重要的是在最有可能的情况下,它会失败或增加不合理的开销。当我写“坏”建议时,我的意思是“最有可能直接有害”的建议,并不是说它不完美。@Cheersandhth.-Alf不用担心,无论如何,你说OP不清楚是对的:)对此有一个评论,但如果你使用此解决方案,请在所有非复制构造函数中将
c
初始化为
nullptr
@约翰说得对。我假设初始化在那里,但为了简单起见,没有给出示例代码。嗯,在循环列表的情况下会发生什么,这里?这也是问题缺乏明确性的一个方面@干杯-阿尔夫许多这样的维度之一:)