使用赋值重载语法复制构造函数?

时间:2016-06-15 11:28:07

标签: c++ constructor copy-constructor

我正在编写五大​​(复制构造函数,复制赋值运算符,移动构造函数,移动赋值运算符,析构函数)。而且我对复制构造函数的语法略有不同。

假设我有一个具有以下私有成员的类foo:

    template<class data> // edit
    class foo{

    private:
        int size, cursor; // Size is my array size, and cursor is the index I am currently pointing at
        data * dataArray; // edit
    }

如果我要为某个任意大小X编写一个构造函数,它看起来就像这样。

template<class data> // edit
foo<data>::foo(int X){
    size = X;
    dataArray = new data[size];
    cursor = 0; // points to the first value
}

现在,如果我想创建另一个名为bar的对象的复制构造函数,我需要进行以下操作:

template<class data> // edit
foo<data>::foo(foo &bar){
foo = bar; // is this correct? 
}

假设我有以下代码中的重载=

 template<class data> // edit
    foo<data>::operator=(foo &someObject){
        if(this != someObject){
            size = someObject.size;
            cursor = someObject.cursor;
            delete[] dataArray;
            dataArray = new data[size];
            for(cursor = 0; cursor<size-1;cursor++)
                 dataArray[cursor] = someObject.dataArray[cursor];
            }

        else
            // does nothing because it is assigned to itself
        return *this;
        }

我的复制构造函数是否正确?或者foo = bar应该是*this = bar

我还是模板化构造函数的新手,所以如果我在代码中出现任何错误,请告诉我我会纠正它。

编辑1:感谢Marcin在下面提供的答案,我对上面的代码进行了一些编辑,使其在语法上更加正确,并使用//edit对它们进行了评论。列表如下:

  1. 以前的template<classname data>,对于函数和类,分别不能是template <typename data>template <class data>
  2. 之前int*dataArray;这会错过模板,应该是data* dataArray;

2 个答案:

答案 0 :(得分:2)

实现所需目标的最佳方法是使用已经处理分配,复制和移动的类,为您处理内存管理。 std::vector正是这样做的,可以直接替换动态分配的数组和大小。执行此操作的类通常称为RAII类。

话虽如此,并假设这是正确实施各种特殊成员函数的练习,我建议您通过copy and swap idiom继续。 (有关详细信息和评论,请参阅SO上的What is the copy and swap idiom?)。我们的想法是根据复制构造函数定义赋值操作。

从成员,构造函数和析构函数开始。这些定义了类成员的所有权语义:

template <class data>
class foo {
 public:
  foo(const size_t n);
  ~foo();

 private:
  size_t size; // array size
  size_t cursor; // current index
  data* dataArray; // dynamically allocated array
};

template <class data>
foo<data>::foo(const size_t n)
 : size(n), cursor(0), dataArray(new data[n])
{}

template <class data>
foo<data>::~foo() {
    delete[] dataArray;
}

这里,内存在构造函数中分配并在析构函数中释放。 接下来,编写复制构造函数。

template <class data>
foo<data>::foo(const foo<data>& other)
 : size(other.size), cursor(other.cursor), dataArray(new data[other.size]) {
     std::copy(other.dataArray, other.dataArray + size, dataArray);
}

(以及类体内的声明foo(const foo& other);)。 请注意它如何使用成员初始化列表将成员变量设置为other对象中的值。执行新分配,然后在复制构造函数的主体中将数据从other对象复制到此对象中。

接下来是赋值运算符。您现有的实现必须执行大量的指针操作,并且不是异常安全的。让我们来看看如何更简单,更安全地完成这项工作:

template <class data>
foo<data>& foo<data>::operator=(const foo<data>& rhs) {
  foo tmp(rhs); // Invoke copy constructor to create temporary foo

  // Swap our contents with the contents of the temporary foo:
  using std::swap;
  swap(size, tmp.size);
  swap(cursor, tmp.cursor);
  swap(dataArray, tmp.dataArray);

  return *this;
}

(以及课堂上的声明,foo& operator=(const foo& rhs);)。

[ - 旁白:您可以通过接受函数参数按值来避免编写第一行(显式复制对象)。这是相同的,在某些情况下可能更有效:

template <class data>
foo<data>& foo<data>::operator=(foo<data> rhs) // Note pass by value!
{
  // Swap our contents with the contents of the temporary foo:
  using std::swap;
  swap(size, rhs.size);
  swap(cursor, rhs.cursor);
  swap(dataArray, rhs.dataArray);

  return *this;
}

但是,如果您还定义了移动赋值运算符,这样做可能会导致模糊的重载。 - ]

这样做的第一件事是创建从中分配的对象的副本。这使用了复制构造函数,因此复制对象的细节只需要在复制构造函数中实现一次。

复制完成后,我们会将内部内容与副本的内部交换。在函数体的末尾,tmp副本超出范围,其析构函数清理内存。但这不是在函数开头分配的内存;在我们将状态与临时状态交换之前,它是我们的对象使用保存的内存。

通过这种方式,分配,复制和解除分配的细节将保留在构造函数和析构函数中。赋值运算符只需复制交换

这还有一个优点,除了更简单之外:它是异常安全的。在上面的代码中,分配错误可能导致在创建临时时抛出异常。但是我们还没有修改类的状态,所以即使赋值失败,我们的状态仍然保持一致(和正确)。

遵循相同的逻辑,移动操作变得微不足道。必须定义移动构造函数以简单地获取资源的所有权并使源(移动的对象)处于明确定义的状态。这意味着将源dataArray成员设置为nullptr,以便其析构函数中的后续delete[]不会导致问题。

移动赋值运算符可以与复制赋值类似地实现,但在这种情况下,对异常安全的关注较少,因为您只是窃取已分配的源对象内存。在完整的示例代码中,我选择简单地交换状态。

可以看到完整,可编辑且可运行的示例here

答案 1 :(得分:1)

您的foo类在内部不使用data模板参数。我想你想在这里使用它:

int * dataArray; // should be: data * dataArray;

您也不得使用classname关键字,typenameclass。您的代码中还有许多其他编译错误。

你的拷贝构造函数错误,它不会编译:

foo = bar; // is this correct? - answer is NO

foo是此上下文中的类名,因此您的假设是正确的。 *this = someObject这可以工作(有了额外的修复,至少dataArray必须设置为nullptr),但你的类变量将首先由复制构造函数默认构造,只能由赋值运算符覆盖,所以它的安静没有效率。如需更多信息,请点击此处:

Calling assignment operator in copy constructor

Is it bad form to call the default assignment operator from the copy constructor?