关于self assignment向大家请教,问题有点长,谢谢!

visame 2007-12-22 12:40:48
对赋值运算符不了解,向大家请教一下。
如下程序可以运行通过:
#include <iostream>
using namespace std;

int main()
{
int i=10;
i=i;//这里有一个self assignment
cout<<i<<endl;
return 0;
}

在看C++ Primer过程中, 第4版13.4. A Message-Handling Example中有如下一个例子:
Message& Message::operator=(const Message &rhs)
{
if (&rhs != this) {
remove_Msg_from_Folders(); // update existing Folders
contents = rhs.contents; // copy contents from rhs
folders = rhs.folders; // copy Folder pointers from rhs
// add this Message to each Folder in rhs
put_Msg_in_Folders(rhs.folders);
}
return *this;
}

"Assignment involves obliterating the left-hand operand." Once the members of the left-hand operand are destroyed, those in the right-hand operand are assigned to the corresponding left-hand members. If the objects were the same, then destroying the left-hand members would also destroy the right-hand members!
第一句也就是说Assignment的左边值要先把自身的内容去掉。(到底什么时候去掉?)第一个例子中的i=i;没有出现错误可以认为是执行了self assignment check的结果吧。(可以这样理解吗?)

可是,后来又有一个例子,13.5.1. Defining Smart Pointer Classes

    HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
++rhs.ptr->use; // increment use count on rhs first
if (--ptr->use == 0)
delete ptr; // if use count goes to 0 on this object, delete it
ptr = rhs.ptr; // copy the U_Ptr object
val = rhs.val; // copy the int member
return *this;
}
文中说:
This assignment operator guards against self-assignment by incrementing the use count of rhs before decrementing the use count of the left-hand operand.
但是,我觉得本质并没有个改变阿。ptr = rhs.ptr;不管怎么说,左边的ptr的内容都要被去掉,如果是self assignment的话,这样赋值就会出错,因为右边的rhs.ptr的内容也被去掉了(左右实际为同一个变量)。
*************************************************************************
再仔细想一想,如果built-in型变量可以self assignment不出错的话,那么class之间的self assignment也应该不会出错,不需要if (&rhs != this)的检查。
理由如下:
因为,class的self assignment最终可以分解为built-in型变量的self assignment。而built-in型变量自己可以解决self assignment问题。

**************************************************************************
我不知道我的分析哪里出了问题,查了一些资料,还是没有明白。
...全文
499 21 打赏 收藏 转发到动态 举报
写回复
用AI写文章
21 条回复
切换为时间正序
请发表友善的回复…
发表回复
visame 2008-02-18
  • 打赏
  • 举报
回复
to A_B_C_ABC:
.............
这里没有指针,contents和folders都不是指针。为什么也要重载赋值运算符呢?
==================
如果你理解了类中有指针,要进行赋值运算符重载,并明白它为什么判断
那么类中拥有资源的句柄要进行赋值运算符重载也是可以理解的啊.那个remove_Msg_from_Folders(); 就相当于类中有指针时,delete指针.

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
“那个remove_Msg_from_Folders(); 就相当于类中有指针时,delete指针.”这句可真难理解阿!
visame 2008-02-18
  • 打赏
  • 举报
回复
核心疑惑点:
"不管怎么说,左边的ptr的内容都要被去掉"这句是错的,例如   int*   i   =   new   int(1);int*   j   =   new   int(2);如果接着执行i   =   j;则之前的i指向的值为1的那块内存就没有释放掉。C中指针赋值的意思是把左边指针指向(即指针的地址赋值为)右边的对象,原来指针所指向的内存还在那里。指针赋值不像一般的对象赋值,对象赋值是对象不动,把对象的值擦除再写新值(例如   int   i   =3;i   =   4;)。

指针赋值好像不是这样理解的吧!
i=j;也是本身把i的内容擦除,然后把j的内容填进去吧!
所以,i=i,如果不执行自己检查的话,也是错误的。
AngelDevil 2008-02-18
  • 打赏
  • 举报
回复
mark
visame 2008-02-18
  • 打赏
  • 举报
回复
to iambic:
Are you SURE?!
iambic 2008-02-18
  • 打赏
  • 举报
回复
>class之间的self assignment也应该不会出错,不需要if(&rhs != this)的检查。
建议有时间读一读《Exceptional C++》。
如果你能遵守“异常安全”,大多数情况下不加这个自我赋值的检查也会产生逻辑上正确的代码。但是可能有效率问题。
visame 2008-02-17
  • 打赏
  • 举报
回复
顶顶顶!
cnzdgs 2008-02-17
  • 打赏
  • 举报
回复
大家说得都有道理,我也来说几句。
“if (&rhs != this)”,是否有必要这样判断,完全取决于类本身,编程要设法确保类的赋值运算不要隐藏问题。
举个简单的例子,假设类中存在一个指针成员m_p,在构造的时候用new为其分配了内存,现在要将另一个指针p赋给它:
如果执行
m_p = p;
则可能导致m_p原本指向的内存无法释放;
如果执行
delete m_p;
m_p = p;
如果p与m_p是相同的指针则导致指针指向的内存被释放了,不能再使用。
所以应该改成:
if (m_p != p)
{
delete m_p;
m_p = p;
}
这样就没问题了。
类中存在指针成员只是一种情况,实际上可能的情况有很多种,例如类中存在句柄需要关闭等等。所以说要根据类的具体情况来确定要如何处理。
visame 2007-12-22
  • 打赏
  • 举报
回复
ltc_mouse
照您这么说,self assignment check应该没有什么作用。可是,各个参考书都强调要self assignment check.
ltc_mouse 2007-12-22
  • 打赏
  • 举报
回复
我觉得问题并不是赋值本身吧。赋值在编译后差不多都是:把某个内存的东西读到寄存器,再把寄存器的内容写到另一个内存里(也许就是刚才读的那个,当然有可能编译器优化后不为这样的代码生成指令),个人认为不需要添加什么check。

再看看remove_Msg_from_Folders()和delete ptr;我们重载赋值运算=,就是希望在赋值前清空原有的内存,以避免内存泄漏,在清空之前我们判断是不是自赋值。但编译器不管这么多,它只会按照你给的代码顺序生成,你不判断,它生成的第一段语句就只能是你写的第一段,释放内存~~
visame 2007-12-22
  • 打赏
  • 举报
回复
自己顶一下,呼唤高手!
sinux_1983 2007-12-22
  • 打赏
  • 举报
回复
你要做的就是保证自定义类,能够模拟出build-in类型的语义行为。

sinux_1983 2007-12-22
  • 打赏
  • 举报
回复
再仔细想一想,如果built-in型变量可以self assignment不出错的话,那么class之间的self assignment也应该不会出错,不需要if (&rhs != this)的检查。
理由如下:
因为,class的self assignment最终可以分解为built-in型变量的self assignment。而built-in型变量自己可以解决self assignment问题。
**************************************************************************
我不知道我的分析哪里出了问题,查了一些资料,还是没有明白。
=================================================================================
你怎么知道built-in型变量不支持这种语义呢,从你的第一个代码来看,在i=i;的过程中,i的值没有被刷掉,说明这时也是支持这种语义的。你怎么保证你自己定义的来也支持这种语义呢?只能用if (&rhs != this)检查了。
霁云 2007-12-22
  • 打赏
  • 举报
回复
需要检查是因为可能类成员变量中有动态分配的内存,这样不加检查的自己赋值会导致,有效信息丢失
visame 2007-12-22
  • 打赏
  • 举报
回复
谢谢A_B_C_ABC 和各位。
Effective C++ 的条款11很早就看过了,感觉什么都没讲,还不如 C++ Primer讲解得清楚。
0黄瓜0 2007-12-22
  • 打赏
  • 举报
回复
.............
这里没有指针,contents和folders都不是指针。为什么也要重载赋值运算符呢?
==================
如果你理解了类中有指针,要进行赋值运算符重载,并明白它为什么判断
那么类中拥有资源的句柄要进行赋值运算符重载也是可以理解的啊.那个remove_Msg_from_Folders(); 就相当于类中有指针时,delete指针.
calss_cyl 2007-12-22
  • 打赏
  • 举报
回复
假设你建立一个class用来保存一个指针指向的一块动态分配的位图(bitmap):
class Bitmap{...};
class Widget{
...
private:
Bitmap* pb;
};
下面的operator=实现代码,是不安全的,
Widget& Widget::operator=(const Widget& rhs)//不安全
{
delete pb; //停止使用当前的bitmap
pb = newBitmap(*rhs.pb);//使用rhs的bitmap版本(复件)
return *this;
}
如果这时用自我赋值的话,delete的时候,不但把当前bitmap delete掉了,也把rhs的bitmap也delete了。这时候,接下来的
pb = newBitmap(*rhs.pb);实际pb指向了一个被删除的对象!

这就是一种错误的情况,所以不能说 self assignment全部不要。

注:Effective C++ 的条款11. 专门讨论了self assignment的问题。

ltc_mouse 2007-12-22
  • 打赏
  • 举报
回复
A_B_C_ABC理解比我透彻啊~~

另外,觉的举的那个HasPtr赋值的例子不是很恰当。不知道我理解对不对

HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
++rhs.ptr-> use;
if(--ptr-> use == 0)
delete ptr;
ptr = rhs.ptr;
val = rhs.val;
return *this;
}
//注意到ptr是指针,按上面的赋值,虽然避免了自赋值,但导致了两个对象拥有了同一个指针
HasPtr obj1, obj2, obj3;
obj3 = obj1; //这里obj3.ptr = obj1.ptr
obj3 = obj2; //这里delete obj3.ptr;将导致obj1错误...
calss_cyl 2007-12-22
  • 打赏
  • 举报
回复
1.
to:第一句也就是说Assignment的左边值要先把自身的内容去掉。(到底什么时候去掉?)第一个例子中的i=i;没有出现错误可以认为是执行了self assignment check的结果吧。(可以这样理解吗?)

reply:什么时候去掉,例如:int i = 1;接着 i = 2;这时候,以前那个1就被去掉了。就是这个意思,在assignment操作符中contents = rhs.contents之前的contents的值被去掉(即修改)了。

2.
to: 但是,我觉得本质并没有个改变阿。ptr = rhs.ptr;不管怎么说,左边的ptr的内容都要被去掉,如果是self assignment的话,这样赋值就会出错,因为右边的rhs.ptr的内容也被去掉了(左右实际为同一个变量)。
reply: "不管怎么说,左边的ptr的内容都要被去掉"这句是错的,例如 int* i = new int(1);int* j = new int(2);如果接着执行i = j;则之前的i指向的值为1的那块内存就没有释放掉。C中指针赋值的意思是把左边指针指向(即指针的地址赋值为)右边的对象,原来指针所指向的内存还在那里。指针赋值不像一般的对象赋值,对象赋值是对象不动,把对象的值擦除再写新值(例如 int i =3;i = 4;)。
#include<iostream>
#include <conio.h>
int main()
{
int i =3;
std::cout<<&i<<std::endl;
i = 4;
std::cout<<&i<<std::endl;//i的地址没有变

int* m = new int(1);
std::cout<<"m="<<m<<std::endl;
int* n = new int(2);
std::cout<<"n="<<n<<std::endl;
m = n;//指针赋值
std::cout<<"m="<<m<<std::endl;//m现在指向n的那块内存了,但是他以前的那块内存依然存在,没有释放

//内存释放略

getch();
return 0;
}
指针赋值不是把指针指向的内存擦除再写新值,而是移动指针。所以ptr = rhs.ptr的意思就是让ptr指向rhs的ptr,ptr原来指向的内存依然存在,所以要在这句之前先delete ptr(if (--ptr->use == 0(这是你程序里面的条件)))。

3.
to: 再仔细想一想,如果built-in型变量可以self assignment不出错的话,那么class之间的self assignment也应该不会出错,不需要if (&rhs != this)的检查。
理由如下: 因为,class的self assignment最终可以分解为built-in型变量的self assignment。而built-in型变量自己可以解决self assignment问题。
reply: 问的这个真感觉混乱的问题。你的意思是:不用进行class的if(&rhs != this)的检查,直接做class的变量的assignment,而且class之间的assignment操作会延伸到class(姑且如ABC)的某个成员(姑且看成built-in型变量int x)的assignment操作的时候(例如this->x = rhs.x),x的assignment操作会自己检验self assignment。我做了一个例子:
#include<iostream>
#include <conio.h>

class ABC
{
int x;
public:
ABC():x(0){}
ABC(int value):x(value){}
ABC& operator=(const ABC& rhs)
{
//不加self assignment检测
this->x = rhs.x;
return *this;
}
};

int main()
{
ABC abc1(1);
std::cout<<"&abc1 = "<<&abc1<<std::endl;
abc1 = abc1;
std::cout<<"&abc1 = "<<&abc1<<std::endl;

getch();
return 0;
}

可以运行,但是感觉加那个self assignment检测,是为了节省时间。

这个问题有点乱。我也晕了。
visame 2007-12-22
  • 打赏
  • 举报
回复
谢谢您的回答!不过,C++ Primer中
第4版13.4. A Message-Handling Example中有如下一个例子:
Message & Message:: operator = ( const Message & rhs)
{
if ( & rhs != this ) {
remove_Msg_from_Folders(); // update existing Folders
contents = rhs.contents; // copy contents from rhs
folders = rhs.folders; // copy Folder pointers from rhs
// add this Message to each Folder in rhs
put_Msg_in_Folders(rhs.folders);
}
return * this ;
}
这里没有指针,contents和folders都不是指针。为什么也要重载赋值运算符呢?您能不能帮忙看看!谢谢!至少给100分。
visame 2007-12-22
  • 打赏
  • 举报
回复
多谢A_B_C_ABC!
看来是一位C++ Primer的忠实Fans阿!对C++ Primer很熟悉。
加载更多回复(1)

65,210

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧