简单和复杂往往却没有明确的界限,比如C创建结构体后,由于分配的内存没有做任何处理,接着就是一堆赋值语句,导致代码行数激增,那些鼠标滚轮经常坏掉的程序员,很大一部分原因皆是此类;
行数多就多呗,语法简约就是效率、直观、自由;
没错,然而人之初,性本惰,那些厌倦了malloc和calloc的人,外加其它原因,开始转向C++程序设计,于是有此文
使用calloc分配内存后初始化为零,不是初始化吗?
是,结构体的初始值,并非都是一样的,如:
SECURITY_ATTRIBUTES sa; // assignment sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE;
显然,C++为偷懒又达到目的提供了很多帮助。
初入C++的童鞋,最先面临的就是构造函数的问题,等一下,构造函数不就是创建时自动调用的函数吗,目的明确何来疑问?
1.调用的究竟是哪个构造函数?
2.基类和成员调用的是哪个构造函数?
3.移动、拷贝、赋值又是声明逻辑?
……
……
正如我所说,C++只会把问题搞复杂;
其实C++有几个标准,C++98/03,C++11,C++14,C++17,C++20等,可惜了,标准文档是付费才有的读(大约15美元);
所幸的是,标准草案是免费的,而且跟标准文档几乎一模一样,当然,考验英文水平的时刻,也随之到来,同时还有耐力的考验。
每当有童鞋问及,我也是链接回复,诸如:
后来我发现,几乎没有人看得下去,最大的原因是:字数太多了(相比C语言可以简化到一张纸)
那么,我们就换一个方式,试!首先,我们定义一个基类,Base.hpp
class Base {
protected:
// for further test
char *ptr;
public:
Base() { printf("Base::Base(), this=%pn", this); }
Base(const Base &rhs) { printf("Base::Base(const Base &rhs), this=%p, &rhs=%pn", this, &rhs); }
Base(Base &&rhs) { printf("Base::Base(Base &&rhs), this=%p, &rhs=%pn", this, &rhs); }
Base(char *p) { printf("Base::Base(char *p), this=%pn", this); this->ptr = p; }
virtual ~Base() { printf("Base::~Base(), this=%pn", this); }
Base &operator=(const Base &rhs)
{
printf("Base::operator=(const Base &rhs), this=%p, &rhs=%pn", this, &rhs);
if (&rhs != this) {}
return *this;
}
Base &operator=(Base &&rhs)
{
printf("Base::operator=(const Base &&rhs), this=%p, &rhs=%pn", this, &rhs);
if (&rhs != this) {}
return *this;
}
};
可以看出,C++有4个基本的构造析构函数,以及两个赋值操作;其中移动构造函数和移动赋值操作是C++11新增的:
Base(Base &&rhs);
Base &operator=(Base &&rhs);
注意printf函数需要stdio.h,然后是作为数据成员的类,MemberObject.hpp
class MemberObject {
public:
MemberObject() { printf("MemberObject::MemberObject(), this=%pn", this); }
MemberObject(const MemberObject &rhs) { printf("MemberObject::MemberObject(const MemberObject &rhs), this=%pn", this); }
MemberObject(MemberObject &&rhs) { printf("MemberObject::MemberObject(MemberObject &&rhs), this=%pn", this); }
virtual ~MemberObject() { printf("MemberObject::~MemberObject(), this=%pn", this); }
MemberObject &operator=(const MemberObject &rhs)
{
printf("MemberObject::operator=(const MemberObject &rhs), this=%pn", this);
if (&rhs != this) {}
return *this;
}
MemberObject &operator=(MemberObject &&rhs)
{
printf("MemberObject::operator=(const MemberObject &&rhs), this=%pn", this);
if (&rhs != this) {}
return *this;
}
};
接着是创建对象的类,Object.hpp
class Object : public Base {
protected:
MemberObject mo;
public:
Object() { printf("Object::Object(), this=%pn", this); }
Object(const Object &rhs) { printf("Object::Object(const Object &rhs), this=%pn", this); }
Object(Object &&rhs) { printf("Object::Object(Object &&rhs), this=%pn", this); }
Object(char *p) { printf("Object::Object(char *p), this=%pn", this); }
virtual ~Object() { printf("Object::~Object(), this=%pn", this); }
Object &operator=(const Object &rhs)
{
printf("Object::operator=(const Object &rhs), this=%pn", this);
if (&rhs != this) {}
return *this;
}
Object &operator=(Object &&rhs)
{
printf("Object::operator=(const Object &&rhs), this=%pn", this);
if (&rhs != this) {}
return *this;
}
Object operator+(const Object &rhs) const
{
printf("Object::operator+(const Object &rhs), this=%pn", this);
Object o1;
return o1;
}
};
程序设计,前瞻性很重要,这里使用模板函数来测试不同创建方式的构造细节:
templatevoid test_cpp() { printf("n"); C o1; printf("n"); C o2 = o1; printf("n"); C o3(o1); printf("n"); C o4(o1 + o2); printf("n"); B *p1 = new C(nullptr); printf("n"); o2 = o1; printf("n"); o3 = o1 + o2; printf("n"); delete p1; printf("n"); }
其中,B表示基类,C表示创建类,现在我们试试:
test_cpp();
结果:
Base::Base(), this=000000573E15F168 MemberObject::MemberObject(), this=000000573E15F178 Object::Object(), this=000000573E15F168 Base::Base(), this=000000573E15F198 MemberObject::MemberObject(), this=000000573E15F1A8 Object::Object(const Object &rhs), this=000000573E15F198 Base::Base(), this=000000573E15F1C8 MemberObject::MemberObject(), this=000000573E15F1D8 Object::Object(const Object &rhs), this=000000573E15F1C8 Object::operator+(const Object &rhs), this=000000573E15F168 Base::Base(), this=000000573E15EFF8 MemberObject::MemberObject(), this=000000573E15F008 Object::Object(), this=000000573E15EFF8 Base::Base(), this=000000573E15F1F8 MemberObject::MemberObject(), this=000000573E15F208 Object::Object(Object &&rhs), this=000000573E15F1F8 Object::~Object(), this=000000573E15EFF8 MemberObject::~MemberObject(), this=000000573E15F008 Base::~Base(), this=000000573E15EFF8 Base::Base(), this=00000159706A25D0 MemberObject::MemberObject(), this=00000159706A25E0 Object::Object(char *p), this=00000159706A25D0 Object::operator=(const Object &rhs), this=000000573E15F198 Object::operator+(const Object &rhs), this=000000573E15F168 Base::Base(), this=000000573E15EFF8 MemberObject::MemberObject(), this=000000573E15F008 Object::Object(), this=000000573E15EFF8 Base::Base(), this=000000573E15F348 MemberObject::MemberObject(), this=000000573E15F358 Object::Object(Object &&rhs), this=000000573E15F348 Object::~Object(), this=000000573E15EFF8 MemberObject::~MemberObject(), this=000000573E15F008 Base::~Base(), this=000000573E15EFF8 Object::operator=(const Object &&rhs), this=000000573E15F1C8 Object::~Object(), this=000000573E15F348 MemberObject::~MemberObject(), this=000000573E15F358 Base::~Base(), this=000000573E15F348 Object::~Object(), this=00000159706A25D0 MemberObject::~MemberObject(), this=00000159706A25E0 Base::~Base(), this=00000159706A25D0 Object::~Object(), this=000000573E15F1F8 MemberObject::~MemberObject(), this=000000573E15F208 Base::~Base(), this=000000573E15F1F8 Object::~Object(), this=000000573E15F1C8 MemberObject::~MemberObject(), this=000000573E15F1D8 Base::~Base(), this=000000573E15F1C8 Object::~Object(), this=000000573E15F198 MemberObject::~MemberObject(), this=000000573E15F1A8 Base::~Base(), this=000000573E15F198 Object::~Object(), this=000000573E15F168 MemberObject::~MemberObject(), this=000000573E15F178 Base::~Base(), this=000000573E15F168
总结:
Object o1;
调用的是默认构造函数,这个没有声明说的了;按基类,成员,派生类顺序调用构造函数,对基类的基类也是同理;
Object o2 = o1;
调用的是拷贝构造函数,而不是赋值操作符;正如我所说,C++只会把问题搞复杂;
Object o3(o1);
同理,调用拷贝构造函数;同时,基类和成员调用的是默认构造函数,只要不显式指定,都是这种构造方式;
Object o4(o1 + o2);
首先创建一个返回值对象,处理后以返回值为参数,调用移动构造函数,然后析构返回值对象;
等一下,堆栈对象不是函数返回时就自动析构了吗?
正如我所说,C++只会把问题搞复杂,你可以理解为,编译器对变量的管理自己实现了一套“引用计数”机制;
Base *p1 = new C(nullptr);
调用自定义构造函数(带参构造),你可以自己定义很多个这样的函数,同样的,未指定的成员和基类会调用默认构造函数,由于析构函数是virtual,我们可以delete基类的指针来delete实际的Object,这里的virtual只是作区别标记,跟普通的虚函数并不一样,正如我所说,C++只会把问题搞复杂,这样的情形还有很多;
o2 = o1;
调用赋值操作符;
o3 = o1 + o2;
调用移动赋值操作符,首先创建返回值,创建临时Object,移动构造,析构返回值,移动赋值,析构临时Object,如下图:
析构函数按派生类,成员,基类顺序调用,即与构造函数顺序相反。
那么,怎么让基类和成员调用一样的构造函数呢?我们再创建一个类,其实是Object复制粘贴修改,Object2.hpp
class Object2 : public Base {
protected:
MemberObject mo;
public:
Object2() { printf("Object2::Object2(), this=%pn", this); }
Object2(const Object2 &rhs) : Base(rhs), mo(rhs.mo) { printf("Object2::Object2(const Object2 &rhs), this=%pn", this); }
Object2(Object2 &&rhs) : Base(rhs), mo(rhs.mo) { printf("Object2::Object2(Object2 &&rhs), this=%pn", this); }
Object2(char *p) : Base(p) { printf("Object2::Object2(char *p), this=%pn", this); }
virtual ~Object2() { printf("Object2::~Object2(), this=%pn", this); }
Object2 &operator=(const Object2 &rhs)
{
printf("Object2::operator=(const Object2 &rhs), this=%pn", this);
if (&rhs != this) {}
return *this;
}
Object2 &operator=(Object2 &&rhs)
{
printf("Object2::operator=(const Object2 &&rhs), this=%pn", this);
if (&rhs != this) {}
return *this;
}
Object2 operator+(const Object2 &rhs) const
{
printf("Object2::operator+(const Object2 &rhs), this=%pn", this);
Object2 o1;
return o1;
}
};
再次调用:
test_cpp();
结果
Base::Base(), this=000000573E15F168 MemberObject::MemberObject(), this=000000573E15F178 Object2::Object2(), this=000000573E15F168 Base::Base(const Base &rhs), this=000000573E15F198, &rhs=000000573E15F168 MemberObject::MemberObject(const MemberObject &rhs), this=000000573E15F1A8 Object2::Object2(const Object2 &rhs), this=000000573E15F198 Base::Base(const Base &rhs), this=000000573E15F1C8, &rhs=000000573E15F168 MemberObject::MemberObject(const MemberObject &rhs), this=000000573E15F1D8 Object2::Object2(const Object2 &rhs), this=000000573E15F1C8 Object2::operator+(const Object2 &rhs), this=000000573E15F168 Base::Base(), this=000000573E15EFF8 MemberObject::MemberObject(), this=000000573E15F008 Object2::Object2(), this=000000573E15EFF8 Base::Base(const Base &rhs), this=000000573E15F1F8, &rhs=000000573E15EFF8 MemberObject::MemberObject(const MemberObject &rhs), this=000000573E15F208 Object2::Object2(Object2 &&rhs), this=000000573E15F1F8 Object2::~Object2(), this=000000573E15EFF8 MemberObject::~MemberObject(), this=000000573E15F008 Base::~Base(), this=000000573E15EFF8 Base::Base(char *p), this=00000159706A2450 MemberObject::MemberObject(), this=00000159706A2460 Object2::Object2(char *p), this=00000159706A2450 Object2::operator=(const Object2 &rhs), this=000000573E15F198 Object2::operator+(const Object2 &rhs), this=000000573E15F168 Base::Base(), this=000000573E15EFF8 MemberObject::MemberObject(), this=000000573E15F008 Object2::Object2(), this=000000573E15EFF8 Base::Base(const Base &rhs), this=000000573E15F348, &rhs=000000573E15EFF8 MemberObject::MemberObject(const MemberObject &rhs), this=000000573E15F358 Object2::Object2(Object2 &&rhs), this=000000573E15F348 Object2::~Object2(), this=000000573E15EFF8 MemberObject::~MemberObject(), this=000000573E15F008 Base::~Base(), this=000000573E15EFF8 Object2::operator=(const Object2 &&rhs), this=000000573E15F1C8 Object2::~Object2(), this=000000573E15F348 MemberObject::~MemberObject(), this=000000573E15F358 Base::~Base(), this=000000573E15F348 Object2::~Object2(), this=00000159706A2450 MemberObject::~MemberObject(), this=00000159706A2460 Base::~Base(), this=00000159706A2450 Object2::~Object2(), this=000000573E15F1F8 MemberObject::~MemberObject(), this=000000573E15F208 Base::~Base(), this=000000573E15F1F8 Object2::~Object2(), this=000000573E15F1C8 MemberObject::~MemberObject(), this=000000573E15F1D8 Base::~Base(), this=000000573E15F1C8 Object2::~Object2(), this=000000573E15F198 MemberObject::~MemberObject(), this=000000573E15F1A8 Base::~Base(), this=000000573E15F198 Object2::~Object2(), this=000000573E15F168 MemberObject::~MemberObject(), this=000000573E15F178 Base::~Base(), this=000000573E15F168
嗯,似乎大部分情形都符合预期,除了:
也就是说,右值(临时的,无名称的) 在移动构造函数或移动赋值操作中,是左值!
正如我所说,C++只会把问题搞复杂!
那么,如何向成员和基类传递右值呢?我们再创建一个类,其实是Object2的复制粘贴修改,Object3.hpp
class Object3 : public Base {
protected:
MemberObject mo;
public:
Object3() { printf("Object3::Object3(), this=%pn", this); }
Object3(const Object3 &rhs) : mo(rhs.mo), Base(rhs) { printf("Object3::Object3(const Object3 &rhs), this=%pn", this); }
Object3(Object3 &&rhs) : Base(std::move(rhs)), mo(std::move(rhs.mo)) { printf("Object3::Object3(Object3 &&rhs), this=%pn", this); }
Object3(char *p) : Base(p) { printf("Object3::Object3(char *p), this=%pn", this); }
virtual ~Object3() { printf("Object3::~Object3(), this=%pn", this); }
Object3 &operator=(const Object3 &rhs)
{
printf("Object3::operator=(const Object3 &rhs), this=%pn", this);
if (&rhs != this) {}
return *this;
}
Object3 &operator=(Object3 &&rhs)
{
printf("Object3::operator=(const Object3 &&rhs), this=%pn", this);
if (&rhs != this) {}
return *this;
}
Object3 operator+(const Object3 &rhs) const
{
printf("Object3::operator+(const Object3 &rhs), this=%pn", this);
Object3 o1;
return o1;
}
};
这里顺便打乱了构造函数后面的冒号的初始化列表,因为这个东西对C语言的童鞋来说,很不自在,而且会有疑惑,初始化列表是不是顺序执行的;
执行:
test_cpp();
结果
Base::Base(), this=000000573E15F168 MemberObject::MemberObject(), this=000000573E15F178 Object3::Object3(), this=000000573E15F168 Base::Base(const Base &rhs), this=000000573E15F198, &rhs=000000573E15F168 MemberObject::MemberObject(const MemberObject &rhs), this=000000573E15F1A8 Object3::Object3(const Object3 &rhs), this=000000573E15F198 Base::Base(const Base &rhs), this=000000573E15F1C8, &rhs=000000573E15F168 MemberObject::MemberObject(const MemberObject &rhs), this=000000573E15F1D8 Object3::Object3(const Object3 &rhs), this=000000573E15F1C8 Object3::operator+(const Object3 &rhs), this=000000573E15F168 Base::Base(), this=000000573E15EFF8 MemberObject::MemberObject(), this=000000573E15F008 Object3::Object3(), this=000000573E15EFF8 Base::Base(Base &&rhs), this=000000573E15F1F8, &rhs=000000573E15EFF8 MemberObject::MemberObject(MemberObject &&rhs), this=000000573E15F208 Object3::Object3(Object3 &&rhs), this=000000573E15F1F8 Object3::~Object3(), this=000000573E15EFF8 MemberObject::~MemberObject(), this=000000573E15F008 Base::~Base(), this=000000573E15EFF8 Base::Base(char *p), this=00000159706A1DF0 MemberObject::MemberObject(), this=00000159706A1E00 Object3::Object3(char *p), this=00000159706A1DF0 Object3::operator=(const Object3 &rhs), this=000000573E15F198 Object3::operator+(const Object3 &rhs), this=000000573E15F168 Base::Base(), this=000000573E15EFF8 MemberObject::MemberObject(), this=000000573E15F008 Object3::Object3(), this=000000573E15EFF8 Base::Base(Base &&rhs), this=000000573E15F348, &rhs=000000573E15EFF8 MemberObject::MemberObject(MemberObject &&rhs), this=000000573E15F358 Object3::Object3(Object3 &&rhs), this=000000573E15F348 Object3::~Object3(), this=000000573E15EFF8 MemberObject::~MemberObject(), this=000000573E15F008 Base::~Base(), this=000000573E15EFF8 Object3::operator=(const Object3 &&rhs), this=000000573E15F1C8 Object3::~Object3(), this=000000573E15F348 MemberObject::~MemberObject(), this=000000573E15F358 Base::~Base(), this=000000573E15F348 Object3::~Object3(), this=00000159706A1DF0 MemberObject::~MemberObject(), this=00000159706A1E00 Base::~Base(), this=00000159706A1DF0 Object3::~Object3(), this=000000573E15F1F8 MemberObject::~MemberObject(), this=000000573E15F208 Base::~Base(), this=000000573E15F1F8 Object3::~Object3(), this=000000573E15F1C8 MemberObject::~MemberObject(), this=000000573E15F1D8 Base::~Base(), this=000000573E15F1C8 Object3::~Object3(), this=000000573E15F198 MemberObject::~MemberObject(), this=000000573E15F1A8 Base::~Base(), this=000000573E15F198 Object3::~Object3(), this=000000573E15F168 MemberObject::~MemberObject(), this=000000573E15F178 Base::~Base(), this=000000573E15F168
掐指一算,以及有很多人打瞌睡,部分人关闭了网页,所以简要总结:
1.使用std::move()转换为右值,需要#include
2.std::move()本质是static_cast<>()
3.初始化列表只提供列表,顺序仍然是基类,成员,派生类顺序构造
下一篇讲一讲模板相关的鲜为人知,不得不说,不可描述,难以启齿的故事
(待续)
暑期编程PK赛 得CSDN机械键盘等精美礼品!


