C++的名堂很多,有构造函数、拷贝构造、拷贝赋值、移动构造、移动赋值,这么设计的目的是啥?啥情况被调用?为了弄清楚这些问题,写了如下示例代码。
#include <vector>
#include <deque>
#include <list>
#include <forward_list>
#include <iostream>
#include <map>
#include <string.h>
#include <assert.h>
using namespace std;
class StrVec {
public:
StrVec() {//默认构造
cout << __LINE__ << "," << __func__ << ", this=0x" << this << endl;
m_count = 0;
m_buf = NULL;
}
StrVec(int count) { //常规构造函数
cout << __LINE__ << "," << __func__ << ", this=0x" << this << endl;
cout << "count:" << count << endl;
m_count = count;
m_buf = (char *)malloc(m_count);
assert(m_buf);
}
~StrVec() {
cout << __LINE__ << "," << __func__ << ", this=0x" << this << endl;
m_count = 0;
if (m_buf) {
free(m_buf);
m_buf = nullptr;
}
}
StrVec(const StrVec &vec) { //拷贝构造函数
cout << __LINE__ << "," << __func__ << ", this=0x" << this << endl;
copyMembersContent(vec, true);//使用拷贝构造函数的对象,说明成员数据没有进行过
//任何初始化赋值(注意成员指针为随机值),因此可以直接拷贝
}
StrVec & operator = (const StrVec &vec) { //拷贝赋值操作符
cout << __LINE__ << "," << __func__ << ", this=0x" << this << endl;
copyMembersContent(vec, false);
return *this; //为了和内置类型的赋值操作保持一致,参考《C++ Primer,5版》444页
}
StrVec(StrVec &&vec) noexcept { //移动构造函数
cout << __LINE__ << "," << __func__ << ", this=0x" << this << endl;
copyMembers(vec);
vec.m_buf = nullptr;
}
StrVec & operator = (StrVec &&vec) { //移动赋值操作符
cout << __LINE__ << "," << __func__ << ", this=0x" << this << endl;
if (this != &vec) {
copyMembers(vec);
vec.m_buf = nullptr;
}
return *this; //为了和内置类型的赋值操作保持一致,参考《C++ Primer,5版》444页
}
void printCount() {
cout << "m_count=" << m_count << endl;
cout << "----------------------------------------" << endl;
}
void copyMembers(const StrVec &vec) {
m_count = vec.m_count; //私有成员,在类函数体内可以通过对象名访问
m_buf = vec.m_buf; //参考《C++ Primer,5版》441页
}
void copyMembersContent(const StrVec &vec, bool isCopyDirectly) {
if (isCopyDirectly) {
m_count = vec.m_count;
this->m_buf = (decltype(this->m_buf))malloc(vec.m_count);
assert(this->m_buf);
memcpy(this->m_buf, vec.m_buf, vec.m_count);
}
else {
if (this->m_count != vec.m_count) {
m_count = vec.m_count; //私有成员,在类函数体内可以通过对象名访问
auto tmpP = realloc(this->m_buf, vec.m_count);//If ptr(arg0) is NULL, the behavior is the same as calling malloc(new_size).
if (tmpP) {
this->m_buf = (decltype(this->m_buf))tmpP;
}
else if (this->m_buf) {
free(this->m_buf);
this->m_buf = nullptr;
cout << __LINE__ << "," << __func__ << ", this=0x" << this << endl;
}
}
if (this->m_buf) {
memcpy(this->m_buf, vec.m_buf, vec.m_count); //参考《C++ Primer,5版》441页
}
}
}
private:
int m_count;
char *m_buf;
};
StrVec getVec1(int count)
{
StrVec vec(count); //不允许返回局部对象的引用或者指针,但是可以返回直接对象,见《C++ Primer,5版》200页
//局部对象,放在堆中,属于临时变量
return vec;
}
StrVec getVec2(int count)
{
StrVec *vec = new StrVec(count); //new出来的对象,放在栈中,属于非临时变量
return *vec;
}
int main(int argc, char *argv[])
{
/*********************************************************************************************/
StrVec str1; //【默认构造】
str1.printCount();
StrVec str2(10); //【常规构造】
str2.printCount();
/*********************************************************************************************/
StrVec str3(str1); //【拷贝构造】过程:先创建了str3对象,数据成员使用默认值,
// (此时并没有调用默认构造函数,因为仅当没有写构造函数时,
// 编译器才会生成默认构造函数,见《C++ Primer,5版》236页;)
// 接着再使用构造函数初始化str3对象;
str3.printCount();
str3 = str2; //【拷贝赋值】
str3.printCount();
/*********************************************************************************************/
StrVec str4(getVec1(14)); //【移动构造】
str4.printCount();
StrVec str5(getVec2(15)); //【拷贝构造】编译器会将 getVec2(18) 返回值直接当做 str5
cout << "str5_addr =" << &str5 << endl;
str5.printCount();
/*********************************************************************************************/
str3 = getVec1(16); //【移动赋值】先熟悉函数值如何被返回的,见《C++ Primer,5版》200页
// 函数返回一个非引用类型的对象,会执行拷贝,见《C++ Primer,5版》441页
str3.printCount();
str3 = getVec2(17); //【移动赋值】
str3.printCount();
/*********************************************************************************************/
StrVec str6 = getVec1(18); //【移动构造】对象初始化不是赋值,赋值是用新值覆盖当前值,见《C++ Primer,5版》39页
str6.printCount();
StrVec str7 = getVec2(19); //【拷贝构造】
str7.printCount();
/*********************************************************************************************/
return 0;
}
运行输出:
20,StrVec, this=0x012FFCDC
m_count=0
----------------------------------------
26,StrVec, this=0x012FFCCC
count:10
m_count=10
----------------------------------------
43,StrVec, this=0x012FFCBC
m_count=0
----------------------------------------
49,operator =, this=0x012FFCBC
m_count=10
----------------------------------------
26,StrVec, this=0x012FFB54
count:14
55,StrVec, this=0x012FFCAC
34,~StrVec, this=0x012FFB54
m_count=14
----------------------------------------
26,StrVec, this=0x0150E988
count:15
43,StrVec, this=0x012FFC9C
str5_addr =012FFC9C
m_count=15
----------------------------------------
26,StrVec, this=0x012FFB54
count:16
55,StrVec, this=0x012FFBAC
34,~StrVec, this=0x012FFB54
61,operator =, this=0x012FFCBC
34,~StrVec, this=0x012FFBAC
m_count=16
----------------------------------------
26,StrVec, this=0x0150E6E8
count:17
43,StrVec, this=0x012FFB9C
61,operator =, this=0x012FFCBC
34,~StrVec, this=0x012FFB9C
m_count=17
----------------------------------------
26,StrVec, this=0x012FFB54
count:18
55,StrVec, this=0x012FFC8C
34,~StrVec, this=0x012FFB54
m_count=18
----------------------------------------
26,StrVec, this=0x0150E8E0
count:19
43,StrVec, this=0x012FFC7C
m_count=19
----------------------------------------
34,~StrVec, this=0x012FFC7C
34,~StrVec, this=0x012FFC8C
34,~StrVec, this=0x012FFC9C
34,~StrVec, this=0x012FFCAC
34,~StrVec, this=0x012FFCBC
34,~StrVec, this=0x012FFCCC
34,~StrVec, this=0x012FFCDC