编译器一般会提供默认的construct,copy construct 跟 copy assignment;如果你声明了对应的构造函数,那么编译器就不再为它构造default构造函数,默认的拷贝构造函数会以“拷贝no1.objectValue内的每一个bits来进行对应的初始化”
编译器生成的copy assignment操作符,其行为基本上与copy构造函数如出一辙,但是会存在一些情况使得编译器拒绝为class生出operator=
举个例子如下
template<class T>
class NamedObject{
public:
NamedObject(std::string &name, const T& value);
private:
std::string &name;
const T objectValue;
};
std::string newDog("Persephone");
std::string oldDog("Satch");
NamedObject<int> p(newDog, 2);
NamedObject<int> s(oldDog, 36);
p = s;
/*
显然编译器拒绝编译赋值,因为对应的一个reference引用是不允许被改变的,但是对应的默认拷贝构造函数,会将每个bits都拷贝过来,更改const也是被拒绝的,所以这样的行为下建议自己编写代码
还存在最后一种情况,即base class的copy assignment是对应的private,然而对应的子类的copy assignment显然是不能被生成的。
*/
拓展一个对应的知识点
深拷贝与浅拷贝
参考博客
举个例子
struct Student
{
string name;
int age;
};
int main()
{
struct Student stu = {"liming", 18};
struct Student stu2 = {"wanger", 20};
stu2 = stu;
cout<<"age is : "<< stu2.age <<endl;
cout<<"name is :"<< stu2.name<<endl;
}
/*
显然这个拷贝是成功的结果也对应变化了
对应的output
age is : 18
name is :liming
再看看下面的例子
*/
struct stu
{
int i;
char c;
char* p;
};
int main()
{
struct stu s1,s2;
char * str = "rabbit is cute";
s1.i = 345;
s1.c = ‘y‘;
s1.p = (char*)str;
s2 = s1;
printf("s2 %d, %c, %s\n", s2.i, s2.c, s1.p);
printf("s1 ptr: %d, s2 ptr : %d\n", s1.p, s2.p);
}
/*
output:
s2 345, y, rabbit is cute
s1 ptr: 7934, s2 ptr : 7934
对应的我们可以看到s1跟s2的ptr指针指向同一块地址
那么我们可以知道对于s1的ptr更改 也等价于 对s2的ptr更改
这说明指针的并没有将内容复制一块给新指针来指向,只是让新指针指向原来的那个内存,这样就相当于,指针在这个复制的过程中只是复制了地址,而不是内容。
缺省拷贝构造函数在拷贝过程中是按字节复制的,对于指针型成员变量只复制指针本身,而不复制指针所指向的目标--浅拷贝。
当然通过上述的描述我们可以修改代码为如下的形式
*/
struct stu
{
int i;
char c;
char* p;
stu operator=(stu& stuTmp)
{
i = stuTmp.i;
c = stuTmp.c;
p = new char(strlen(stuTmp.p) + 1);
strcpy(p, stuTmp.p);
return *this;
};
};
int main()
{
struct stu s1,s2;
char * str = "rabbit is cute";
s1.i = 345;
s1.c = ‘y‘;
s1.p = (char*)str;
s2 = s1;
printf("s2 %d, %c, %s\n", s2.i, s2.c, s1.p);
printf("s1 ptr: %d, s2 ptr : %d\n", s1.p, s2.p);
}
/*
测试代码如下:
*/
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
class stu
{
public:
int i;
char c;
char* p;
stu operator=(stu& stuTmp)
{
this->i = stuTmp.i;
this->c = stuTmp.c;
this->p = new char(strlen(stuTmp.p) + 1);
for (int i = 0; i < strlen(stuTmp.p); i++)
{
this->p[i] = stuTmp.p[i];
}
//strcpy(pp, stuTmp.p);
return *this;
};
};
int main()
{
struct stu s1, s2;
char * str = "rabbit is cute";
s1.i = 345;
s1.c = ‘y‘;
s1.p = (char*)str;
s2 = s1;
printf("s2 %d, %c, %s\n", s2.i, s2.c, s1.p);
printf("s1 ptr: %d, s2 ptr : %d\n", s1.p, s2.p);
cin.get();
}
/*
output:
相当于重载operator=方法,这样还是运行,产生的结果就是这样的:
s2 345, y, rabbit is cute
s1 ptr: 7910, s2 ptr : 1050000
此时s1和s2中的指针p指向了不同的地址,可以打印一下此时这两个指针的内容是否一样,加入一下代码:
printf("s1 ptr: %s, s2 ptr : %s\n ", s1.p, s2.p);??
产生的结果是:s1 ptr: rabbit is cute, s2 ptr : rabbit is cute?
此时s1和s2中的p指针地址不同,但是指向的内容一致,所以这拷贝成功。?
其实类的结构和上面的结构体是类似的,其实可以将结构体看成一个类来处理,结构体也可有自己的构造、析构、重载运算符河函数,可以简单的认为结构体是类的一种形式。
拷贝有两种:深拷贝,浅拷贝
当出现类的等号赋值时,会调用拷贝函数 在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。当数据成员中没有指针时,浅拷贝是可行的。 但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象。 所以,这时,必须采用深拷贝。 深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。 简而言之,当数据成员中有指针时,必须要用深拷贝。?
?建议:
我们在定义类或者结构体,这些结构的时候,最后都重写拷贝构造函数,避免浅拷贝这类不易发现但后果严重的错误产生。?
*/
你能想到的第一条方法就是使得对应的函数声明为private,那么使得成功阻止人们使用它,但是这种做法并没有绝对安全
因为对应的menber function和friend函数还是可以调用对应的private
当然我们有了上述的class定义,当客户企图拷贝HomeForSale对象,编译器会阻止他,如果你不慎在member函数或friend函数之内这么做,轮到连接器发出抱怨
只要将copy构造函数和copy assignment操作符声明为private就可以办到,但不是在HomeForSale只身,而是在一个专门为了阻止copying动作设计的base class内
class Uncopyable{
protected:
Uncopyable(){}
~Uncopyable(){}
private:
Uncopyable(const Uncopyable&);
Uncopyable & operator= (const Uncopyable&);
};
class HomeForSale: private Uncopyable{
};
/*
这样的处理是非常好的,因为任何人 甚至是member或者friend函数 尝试拷贝HomeForSale对象 编译器会试着生成一个对应的copy constructor或者一个copy assignment操作符,而正如条款12所说,这些函数的“编译生成版”会常会调用其base class的对应兄弟,而对应的那些调用会被编译器拒绝,因为其base class的拷贝函数是private
*/
C++中,当derived class对象被一个对应的指向的base class point delete的时候,那么该base class带了一个non-virtual deconstruct 那么这是未定义行为,实际执行的时候通常发生的是对象的derived成分没被销毁。
当然这会造成一种诡异的“局部销毁”对象,显然这是形成资源泄漏的一种行为。
当然解决这种问题是非常简单的,你可以给出一个对应的virtual deconstructor function
举个例子
class TimeKeeper{
public:
TimeKeeper();
virtual ~TimeKeeper();
};
TimeKeeper * ptk = getTimeKeeper();
delete ptk;
如果一个类class不作为base class的话,令其析构函数为virtual往往是个馊主意
class Point{
public:
Point(int xCoord, int yCoord);
~Point();
private:
int x, y;
};
/*
我还以为有什么高大上的理由,其实对应的理由就是因为使用virtual会导致出现vptr与vtbl,这样会导致增加一个指针的大小,那么会出现对应的字节对齐来导致空间变大
*/
注意不要去继承对应的STL,因为这些容器的deconstruct 都是对应的non-virtual deconstruct
谈谈对应的deconstruct的运作方式,最深层派生的那个class其析构函数最先被调用,编译器会在AWOV的derived classes的析构函数中创建一个对~AWOV的调用动作,所以你必须为这个函数提供一份定义。如果不这么做,连接器会发出对应的抱怨。
显然还是举例子吧
class Transaction{
public:
Transaction();
virtual void logTransaction() const = 0;
};
Transaction::Transaction(){
logTransaction();
}
class BuyTransaction: public Transaction{
public:
virtual void logTransaction() const;
}
class SellTransaction: public Transaction{
public:
virtual void logTransaction() const;
};
BuyTransaction b;
/*
显然这里有个BuyTransaction构造函数被调用,但首先Transaction构造函数一定会被更早的调用,那么derived class对象内的base class成分会在derived class只身成分被构造前先被构造,但是Transaction构造函数的最后一行调用virtual函数logTransaction,这个时候调用的logTransation是logTransaction内的版本,并不是BuyTransaction的版本
好吧,这个问题的出现其实就是因为base class构造期间virtual函数不会下降到对应的derived classes阶级
在base class constructor期间 virtual函数不是virtual函数
显然这是有理由的
因为base class的构造函数在执行的时候,derived class的成员肯定还没有初始化,如果这个期间的virtual函数下降到对应的derived class,要知道derived class的函数几乎是肯定会使用local 成员变量的,那么这些成员变量肯定还没有初始化
然后候捷大佬说:这是一张通往不明确行为和彻夜调试大会的直达车票(xswl)
看完了construct调用virtual 我们接下来看看deconstruct调用virtual
我们熟知析构函数是从最深的derived class进行对应的析构,如果在base class的deconstruct对应有virtual,那么derived class析构已经执行完毕,那么对象内的derived class 成员变量便呈现未定义值,所以c++调用不到,如果可以访问的话,那么就出现对应的问题了,所以进入base class析构函数后对象也就成为了一个base class对象,而c++的任何部分包括virtual函数,dynamic_cast等等也都是如此看待他的
当然会存在方案来解决这个问题 具体看书哈
令derived class将必要的构造信息向上传递到base class构造函数
*/
关于赋值可以写成,连锁的形式
int x, y, z;
x = y = z = 25;
x = (y = (z = 15));
//为了实现连锁赋值,赋值操作符必须返回一个reference指向操作符的左侧实参
class Widget{
public:
.....
Widget& operator=(const Widget & rhs){
...
return* this;
}
}
class Widget{
public:
Widget& opertor+=(const Widget& rhs){
...
return *this;
}
Widget & operator= (int rhs){
return *this;
}
}
原文:https://www.cnblogs.com/qq136155330/p/12606641.html