我正在编写五大(复制构造函数,复制赋值运算符,移动构造函数,移动赋值运算符,析构函数)。而且我对复制构造函数的语法略有不同。
假设我有一个具有以下私有成员的类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
对它们进行了评论。列表如下:
template<classname data>
,对于函数和类,分别不能是template <typename data>
或template <class data>
。 int*dataArray;
这会错过模板,应该是data* dataArray;
答案 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
关键字,typename
或class
。您的代码中还有许多其他编译错误。
你的拷贝构造函数错误,它不会编译:
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?