复制构造函数和赋值运算符实现选择 -

Mik*_*eyG 6 c++ inheritance constructor deep-copy assignment-operator

我最近重新访问了复制构造函数,赋值运算符,这里看到的复制交换idom: 什么是复制和交换习惯用法? 和许多其他地方 -

上面的链接是一个很好的帖子 - 但我还有一些问题 - 这些问题可以在很多地方,stackoverflow和许多其他网站上得到解答,但我没有看到很多一致性 -

1 - 您是否应该try- catch在我们为复制构造函数中的深层复制分配新内存的区域周围?(我已经看到了两种方式)

2 - 关于复制构造函数和赋值运算符的继承,何时应该调用基类函数,何时这些函数应该是虚函数?

3 - std::copy复制构造函数中复制内存的最佳方法是什么?我已经看过了memcpy,看到其他人说memcpy世界上最糟糕的事情.


考虑以下示例(感谢所有反馈),它提示了一些其他问题:

4 - 我们应该检查自我分配吗?如果是这样的话

5 - 关闭主题问题,但我看到swapped用作: std::copy(Other.Data,Other.Data + size,Data); 应该是: std::copy(Other.Data,Other.Data + (size-1),Data); 如果swap从'First到Last'而第0个元素是Other.Data?

6 - 为什么注释掉的构造函数不起作用(我必须将大小更改为mysize) - 假设这意味着无论我编写它们的顺序如何,构造函数将始终首先调用分配元素?

7 - 对我的实施还有其他评论吗?我知道代码没用,但我只想说明一点.

class TBar
{

    public:

    //Swap Function        
    void swap(TBar &One, TBar &Two)
    {
            std::swap(One.b,Two.b);
            std::swap(One.a,Two.a);
    }

    int a;
    int *b;


    TBar& operator=(TBar Other)
    {
            swap(Other,*this);
            return (*this);
    }

    TBar() : a(0), b(new int) {}                //We Always Allocate the int 

    TBar(TBar const &Other) : a(Other.a), b(new int) 
    {
            std::copy(Other.b,Other.b,b);
            *b = 22;                                                //Just to have something
    }

    virtual ~TBar() { delete b;}
};

class TSuperFoo : public TBar
{
    public:

    int* Data;
    int size;

    //Swap Function for copy swap 
    void swap (TSuperFoo &One, TSuperFoo &Two)
    {
            std::swap(static_cast<TBar&>(One),static_cast<TBar&>(Two));
            std::swap(One.Data,Two.Data);
            std::swap(One.size,Two.size);
    }

    //Default Constructor
    TSuperFoo(int mysize = 5) : TBar(), size(mysize), Data(new int[mysize]) {}
    //TSuperFoo(int mysize = 5) : TBar(), size(mysize), Data(new int[size]) {}                *1

    //Copy Constructor
    TSuperFoo(TSuperFoo const &Other) : TBar(Other), size(Other.size), Data(new int[Other.size])        // I need [Other.size]! not sizw
    {
            std::copy(Other.Data,Other.Data + size,Data);        // Should this be (size-1) if std::copy is First -> Last? *2
    }

    //Assignment Operator
    TSuperFoo& operator=(TSuperFoo Other)
    {
            swap(Other,(*this));
            return (*this);
    }

    ~TSuperFoo() { delete[] Data;}

};
Run Code Online (Sandbox Code Playgroud)

Ant*_*ams 4

  1. 如果分配内存,则需要确保在引发异常的情况下释放内存。您可以使用显式try/来执行此操作catch,也可以使用智能指针(例如std::unique_ptr保存内存),当智能指针被堆栈展开销毁时,该内存将被自动删除。

  2. 你很少需要virtual赋值运算符。如果您正在进行成员赋值,请在成员初始化列表中调用基类复制构造函数,并在派生赋值运算符中首先调用基类赋值运算符 --- 如果您正在进行复制/交换,则无需调用派生赋值运算符中的基类赋值,前提是复制和交换正确实现。

  3. std::copy与对象一起工作,并将正确调用复制构造函数。如果您有普通的 POD 对象,那么也memcpy可以正常工作。不过,在大多数情况下我都会选择std::copy——无论如何,对于 POD 来说,它应该在memcpy幕后进行优化,并且它可以避免您稍后添加复制构造函数时可能出现的错误。

[更新问题的更新]

  1. 使用复制/交换编写时,不需要检查自分配,实际上也没有办法这样做——当您输入赋值运算符时,它other就是一个复制,并且您无法知道源对象是什么曾是。这只是意味着自分配仍然会进行复制/交换。

  2. std::copy采用一对迭代器(first、first+size)作为输入。这允许空范围,并且与标准库中每个基于范围的算法相同。

  3. 注释掉的构造函数不起作用,因为成员按照声明的顺序进行初始化,而不管成员初始值设定项列表中的顺序如何。因此,Data总是首先初始化。如果初始化取决于,size那么它将获得一个 duff 值,因为size尚未初始化。如果你交换 的声明sizedata那么这个构造函数将正常工作。好的编译器会警告成员初始化的顺序与声明的顺序不匹配。