是结构体中的复制构造函数,该结构体包含动态分配的数组,总是必需的
Is a copy constructor within a struct which contains a dynamically allocated array always necessary?
我在不同的网站上阅读了一些关于复制构造函数的教程,维基百科,也浏览了"复制构造函数"搜索结果的前5页,但我仍然没有得到它:
我有两个问题:
1. 复制构造函数到底是做什么的?
2. 复制构造函数总是必需的(在包含动态分配数组的结构中)?
我将试着用一个例子来解释是什么困扰着我,以及我实际上在问什么:
假设我不想使用c++字符串,而是创建我自己的"智能"字符串。我只提供问题所需的代码:
#include <iostream>
#include <cstdlib>
using namespace std;
struct MyString
{
char* str;
int n , i;
MyString(int x, char c)
{
str=(char*)malloc(x+1);
for(n=0; n<x; ++n) str[n] = c;
str[n] = ' ';
}
void print()
{
cout << str << endl;
}
void append(MyString second)
{
str=(char*)realloc(str, (n + second.n + 1) * sizeof(char));
for(i=0; i < second.n; ++i) str[n+i] = second.str[i];
n += second.n;
str[n] = ' ';
}
~MyString()
{
cout << "destructor!" << endl;
free(str);
}
};
int main()
{
MyString A(5, '$'), B(5, '#');
A.print();
B.print();
A.append(B);
A.print();
B.print();
A.append(B);
A.print();
B.print();
}
为了简单起见,该结构体只包含一个构造函数,对于给定的整数"n"和给定的字符"c",该构造函数产生一个字符串,该字符串包含字符"c"重复"n"次。示例:MyString A(5, '$');表示A是字符串"$$$$$"。print函数将字符串打印到屏幕上,append函数将一个字符串附加到另一个字符串上。例如:A.append (B);意味着= A + B或在这个例子 "$$$$$" + "#####" = "$$$$$#####".
有几件事需要注意:
1. MyString包含一个动态分配的数组。2. 函数append的形参是"MyString"。
3.我没有包含复制构造函数。
append函数声明如下:
void append(MyString second)
通常这意味着函数append接收到MyString类型对象的副本,但由于MyString包含一个动态分配的对象,该函数将接收到指向原始对象的指针的副本(如果我是正确的?)并将其视为本地副本,这意味着在执行追加操作后,将调用该指针的析构函数并销毁该对象,因此查看我的原始main函数:
int main()
{
MyString A(5, '$'), B(5, '#');
A.print();
B.print();
A.append(B);
// B doesn't exist anymore
A.print(); // OK
B.print(); // ???
A.append(B); // ???
A.print(); // ???
B.print(); // ???
}
为了解决这个问题,我可以写一个复制构造函数,但是我真的需要吗?我可以这样写append函数:
void append(MyString const& second)
还是
void append(MyString& second)
,这是有效的,但我被告知,每次我遇到一个对象,涉及动态分配+一个函数,有对象类型作为参数,我应该写一个复制构造函数,只是为了安全。但会出什么问题呢?如果我不添加复制构造函数,用户会做些什么把事情搞砸呢?
我可以这样写一个复制构造函数:
MyString(MyString const& second)
{
n = second.n;
str = (char*)malloc(n);
for(i=0; i<n; ++i) str[i] = second.str[i];
}
然后我可以让函数append保持原来的形式
void append(MyString second)
但是当执行下面这行代码时到底发生了什么?
A.append(B);
我被告知构造函数没有返回值。因此,如果B在函数append(在A内)执行之前调用了他的复制构造函数,那么B究竟是如何"告诉"A去哪里寻找B的副本的呢?
现在我看到这个问题已经太大了:(所以我现在就到此为止。欢迎任何编辑、建议、评论和回答!提前感谢!
复制构造函数包含从另一个MyString
对象生成MyString
对象所需的指令。
在这种情况下,你绝对需要一个复制构造函数,否则类型MyString
的语义将是非标准的,并且很容易出错。
编译器会自动为你实现一个复制构造函数。不幸的是,它很可能是错误的,因为对象有指针。这个自动实现将只是复制指针的值到另一个MyString
。然后,您将拥有两个认为它们拥有相同内存的MyString
对象。其中一个将被销毁(它的析构函数将运行),而另一个将留下一个不再有效的指针。考虑以下内容:
MyString first(10, 'a');
{ //This is here to create a new scope for the second myString
MyString second(first); //The copy constructor that the compiler made for you runs here
//. . . some other stuff
} //Right here, second goes out of scope and it's destructor runs. This calls delete on str
//Now you're in trouble - first's str pointer now points to unallocated memory
first.print(); //Uh-oh - undefined behavior.
或:
//Declaration:
void SomeFunctionThatPassesByValue(MyString anotherMyString);
. . .
MyString first(10, 'a');
SomeFunctionThatPassesByValue(first); //The copy-constructor can run here too
/* Inside SomeFunctionThatPassesByValue, there will be a copy of first named
anotherMyString which will have its destructor run and call delete and de-allocate
your memory out from under you */
first.print(); //Uh-oh again!
现在,你可以说"任何使用MyString的人:都要非常小心,不要按值复制它,否则事情会搞砸"- 但这不是很现实。你应该显式地禁用复制构造函数(这是Hans Passant在评论中的建议),或者你应该正确地实现它。
你还需要正确地实现(或禁用)复制赋值操作符,否则你会遇到同样的问题。
MyString second(10, 'b');
second = first; //The copy-assign operator that the compiler made for you is also wrong!
对于如何"正确"实现复制构造函数和复制赋值操作符,您有一些选项。最简单的方法就是分配更多的内存并复制指向的内存。如果指向的内存是不可变的(它不是在您的示例中),那么您可以共享指针和计数引用,并仅在使用该指针的最后一个MyString被销毁时调用delete,但这很难得到正确的
- 按结构体的特定成员对结构体数组进行排序
- 如何调用一个函数,它在一个单独的类中打印出一个结构体数组
- 如何创建一个结构体数组
- 初始化用于pthread的结构体数组
- 如何将文件的行读入结构体数组
- 结构体数组,非常量大小
- 无法将结构体数组传递给函数
- 确定它用作参数的结构体数组的大小
- 如何将结构体数组的单个成员数组传递给函数以执行线性搜索
- 如何使用std::binary_search或std::sort对结构体数组进行排序
- c++中对结构体数组使用排序选择
- 在c++中将结构体数组平整化为多个数组
- 在c++中创建结构体数组
- 不能操作由字符串组成的结构体数组
- 传递结构体数组的预期非限定id错误
- 将c++中的结构体数组传递给swift
- 尝试创建一个函数,将数据从文件读取到结构体数组中
- 在c++中创建结构体数组
- 添加并列出一个结构体数组
- Arduino编程中的结构体数组