6

我最近重新访问了此处看到的复制构造函数、赋值运算符、复制交换惯用语: 什么是复制和交换惯用语? 和许多其他地方——

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

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

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

3 - std::copy在复制构造函数中复制内存的最佳方法是什么?我已经看到它memcpy,并且看到其他人说memcpy地球上最糟糕的事情。


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

4 - 我们应该检查自我分配吗?如果是在哪里

5 - 题外话,但我已经看到交换用作: std::copy(Other.Data,Other.Data + size,Data); 应该是: std::copy(Other.Data,Other.Data + (size-1),Data); 如果交换从“第一个到最后一个”并且第 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;}

};
4

4 回答 4

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

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

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

[更新问题的更新]

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

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

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

于 2012-06-26T14:04:43.137 回答
3

1 - 您是否应该在复制构造函数中为深拷贝分配新内存的区域周围尝试捕获?

一般来说,只有在可以处理的情况下才应该捕获异常。如果你有办法在本地处理内存不足的情况,那就抓住它;否则,放手吧。

如果构造失败,你当然不应该从构造函数中正常返回——这会使调用者得到一个无效的对象,并且无法知道它是无效的。

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

构造函数不能是虚函数,因为虚函数只​​能由对象调度,并且在创建它之前没有对象。通常,您也不会将赋值运算符设为虚拟;可复制和可分配类通常被视为非多态“值”类型。

通常,您会从初始化列表中调用基类复制构造函数:

Derived(Derived const & other) : Base(other), <derived members> {}

如果您使用的是复制和交换习语,那么您的赋值运算符就不需要担心基类;这将由交换处理:

void swap(Derived & a, Derived & b) {
    using namespace std;
    swap(static_cast<Base&>(a), static_cast<Base&>(b));
    // and swap the derived class members too
}
Derived & Derived::operator=(Derived other) {
    swap(*this, other);
    return *this;
}

3 -std::copy在复制构造函数中复制内存的最佳方法是什么?我已经看到它memcopy,并且看到其他人说memcopy地球上最糟糕的事情。

处理原始内存是相当不寻常的。通常您的类包含对象,并且通常不能通过简单地复制它们的内存来正确复制对象。您使用对象的复制构造函数或赋值运算符来复制对象,std::copy并将使用赋值运算符来复制对象数组(或更一般地,对象序列)。

如果你真的想要,你可以memcpy用来复制POD(plain old data)对象和数组;但std::copy更不容易出错(因为您不需要提供对象大小),更不易碎(因为如果您将对象更改为非 POD,它不会损坏)并且可能更快(因为对象大小和对齐方式在编译时已知)。

于 2012-06-26T14:09:32.407 回答
1
  1. try-catch当您必须撤消某些操作时可以使用。否则,只需让bad_alloc传播到调用者。

  2. 调用基类的复制构造函数或赋值运算符是让处理其复制的标准方法。我从未见过虚拟赋值运算符的用例,所以我猜它们很少见。

  3. std::copy具有正确复制类对象的优点。memcpy它可以处理的类型相当有限。

于 2012-06-26T14:05:40.200 回答
1
  1. 如果您要进行深度复制的构造函数可能会抛出您可以处理的东西,请继续抓住它。不过,我只是让内存分配异常传播。
  2. 复制构造函数(或任何构造函数)不能是虚拟的。包括这些的基类初始化程序。复制赋值运算符应该委托给基类,即使它们是虚拟的。
  3. memcpy()对于在 C++ 中复制类类型来说太低级了,并且可能导致未定义的行为。我认为std::copy通常是更好的选择。
于 2012-06-26T14:01:48.690 回答