Effective c++学习笔记——条款11:在operateor=中自我赋值
2011-09-12 12:19
483 查看
Handle assignment to self in operator=
本条款的核心是关于c++对象的自我赋值,既然说是自我赋值,那么就会产生一些你意想不到的问题。首先看一下很有意思的“自我赋值”,简单例子
// self_opera.cpp : 定义控制台应用程序的入口点。
//2011/9/11-by wallwind-in revenco
#include "stdafx.h"
#include<iostream>
using namespace std;
class myClass { };
int _tmain(int argc, _TCHAR* argv[]) {
myClass my;
my = my;
system("pause");
return 0;
}
上段程序是可以通过的。可能有时候自我赋值是不能一眼就看出来的。比如以下程序语句:
a[i] = a[j];
//潜在的自我赋值
当i=j的时候,这便是个自我赋值
又如
*px
= *py;
//潜在的自我赋值
如果*px和*py恰好指向同一个东西,这也是自我赋值;
这些并不明显的自我赋值,是“别名”带来的结果:所谓“别名”就是“有一个以上的方法指称(指涉)某对象”。一般而言如果某段代码操作pointers或references而它们被用来“指向多个相同类型的对象”,就需要考虑这些对象是否为同一个。实际上两个对象只要来自同一个继承体系,它们甚至不需要声明为相同类型就可能造成“别名”,因为一个base class的reference或pointer可以指向一个derived
class对象:
就如以下代码:
// self_opera.cpp : 定义控制台应用程序的入口点。
//2011/9/11-by wallwind-in revenco
#include
"stdafx.h"
#include
<iostream>
#include
<stdlib.h>
#include
<string.h>
using
namespace
std;
class
Base{ };
class
Derived:public
Base {};
void
dosomething(const
Base &rb, Derived& pb )
{
cout<<&rb<<endl;
cout<<&pb<<endl;
}
int
_tmain(int
argc, _TCHAR* argv[])
{
const Base
rb;
Derived pb;
dosomething(rb,pb);
return 0;
}
输出结果为:
其实自我赋值的情况是很容易出现问题的。
下面给大家举一个比较简单实用的,类似于书中的例子,myclass内部维护char指针类型,并指向一块内存空间,在进行operator操作时,首先释放当前myclass类型buffer所指向的空间,并将buffer指向赋值右边同一空间。如果是指向同一个myclass类型呢?buffer所指向空间已经被释放,调用ToString方法时访问了已释放的空间,其结果未有定义。
// self_opera.cpp : 定义控制台应用程序的入口点。
//2011/9/11-by wallwind-in revenco
#include
"stdafx.h"
#include
<stdlib.h>
#include
<string.h>
class
myclass{
public:
myclass() {
buffer = new
char[255];
memset(buffer, 65, sizeof(char) * 255);
}
~myclass() {
delete[] buffer;
}
char* ToString() const {
return buffer; }
myclass& operator=(const
myclass& rhs) {
delete[] buffer;
buffer = rhs.buffer;
return *this;
}
private:
char *buffer;
};
int
_tmain(int
argc, _TCHAR* argv[]) {
myclass str1;
printf("%sn", str1.ToString());
str1 = str1;
printf("%sn", str1.ToString());
system("pause");
return 0;
}
欲阻止这种错误,传统做法是由operator=最前面的一个“证同测试”达到“自我赋值”的检验目的:
如下代码
myclass& operator=(const String& rhs) {
if (rhs == * this)////////////////////////////此处要重写“==”
return *this;
delete[] buffer;
buffer = rhs.buffer;
return *this;
}
这样做行得通。稍早我曾经提过,前一版operator=不仅不具备“自我赋值安全性”,也不具备“异常安全性”,这个新版本仍然存在异常方面的麻烦。比如书中的例子给出这样表述,如果, rhs.buffer;
是一块空地址,或者异常地址,依然会出现以上情况。那么就有了另一种技术保证异常的自我赋值了。
在operator=函数内手工排列语句(确保代码不但“异常安全”而且“自我赋值安全”)的一个替代方案是,使用所谓的copy and swap技术。这个技术和“异常安全性”有密切关系,所以由条款29详细说明。然而由于它是一个常见而够好的operator=撰写办法,所以值得看看其实现手法像什么样子:
// self_opera.cpp : 定义控制台应用程序的入口点。
//2011/9/11-by wallwind-in revenco
#include
"stdafx.h"
#include
<iostream>
#include
<stdlib.h>
#include
<string.h>
using
namespace
std;
class
myclass {
public:
myclass() {
_buffer = new
char[255];
memset(_buffer, 65, sizeof(char) * 255);
}
myclass(const myclass& rhs) {
_buffer = new
char[255];
memcpy(_buffer, rhs._buffer, sizeof(char) * 255);
}
myclass& operator=(const
myclass& rhs) {
myclass temp(rhs);
swap(temp);
return *this;
}
char* ToString() const {
return _buffer; }
private:
void swap(const
myclass& rhs) {
delete[] _buffer;
_buffer = rhs._buffer;
}
char *_buffer;
};
int
_tmain(int
argc, _TCHAR* argv[]) {
myclass str1;
printf("%sn", str1.ToString());
str1 = str1;
printf("%sn", str1.ToString());
system("pause");
return 0;
}
我个人比较忧虑这个做法,我认为它为了伶俐巧妙的修补而牺牲了清晰性。然而将“copy 动作”从函数本体内移至“函数参数构造阶段”却可令编译器有时生成更高效的代码。
请记住:
1、确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。
2、确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
本条款的核心是关于c++对象的自我赋值,既然说是自我赋值,那么就会产生一些你意想不到的问题。首先看一下很有意思的“自我赋值”,简单例子
// self_opera.cpp : 定义控制台应用程序的入口点。
//2011/9/11-by wallwind-in revenco
#include "stdafx.h"
#include<iostream>
using namespace std;
class myClass { };
int _tmain(int argc, _TCHAR* argv[]) {
myClass my;
my = my;
system("pause");
return 0;
}
上段程序是可以通过的。可能有时候自我赋值是不能一眼就看出来的。比如以下程序语句:
a[i] = a[j];
//潜在的自我赋值
当i=j的时候,这便是个自我赋值
又如
*px
= *py;
//潜在的自我赋值
如果*px和*py恰好指向同一个东西,这也是自我赋值;
这些并不明显的自我赋值,是“别名”带来的结果:所谓“别名”就是“有一个以上的方法指称(指涉)某对象”。一般而言如果某段代码操作pointers或references而它们被用来“指向多个相同类型的对象”,就需要考虑这些对象是否为同一个。实际上两个对象只要来自同一个继承体系,它们甚至不需要声明为相同类型就可能造成“别名”,因为一个base class的reference或pointer可以指向一个derived
class对象:
就如以下代码:
// self_opera.cpp : 定义控制台应用程序的入口点。
//2011/9/11-by wallwind-in revenco
#include
"stdafx.h"
#include
<iostream>
#include
<stdlib.h>
#include
<string.h>
using
namespace
std;
class
Base{ };
class
Derived:public
Base {};
void
dosomething(const
Base &rb, Derived& pb )
{
cout<<&rb<<endl;
cout<<&pb<<endl;
}
int
_tmain(int
argc, _TCHAR* argv[])
{
const Base
rb;
Derived pb;
dosomething(rb,pb);
return 0;
}
输出结果为:
其实自我赋值的情况是很容易出现问题的。
下面给大家举一个比较简单实用的,类似于书中的例子,myclass内部维护char指针类型,并指向一块内存空间,在进行operator操作时,首先释放当前myclass类型buffer所指向的空间,并将buffer指向赋值右边同一空间。如果是指向同一个myclass类型呢?buffer所指向空间已经被释放,调用ToString方法时访问了已释放的空间,其结果未有定义。
// self_opera.cpp : 定义控制台应用程序的入口点。
//2011/9/11-by wallwind-in revenco
#include
"stdafx.h"
#include
<stdlib.h>
#include
<string.h>
class
myclass{
public:
myclass() {
buffer = new
char[255];
memset(buffer, 65, sizeof(char) * 255);
}
~myclass() {
delete[] buffer;
}
char* ToString() const {
return buffer; }
myclass& operator=(const
myclass& rhs) {
delete[] buffer;
buffer = rhs.buffer;
return *this;
}
private:
char *buffer;
};
int
_tmain(int
argc, _TCHAR* argv[]) {
myclass str1;
printf("%sn", str1.ToString());
str1 = str1;
printf("%sn", str1.ToString());
system("pause");
return 0;
}
欲阻止这种错误,传统做法是由operator=最前面的一个“证同测试”达到“自我赋值”的检验目的:
如下代码
myclass& operator=(const String& rhs) {
if (rhs == * this)////////////////////////////此处要重写“==”
return *this;
delete[] buffer;
buffer = rhs.buffer;
return *this;
}
这样做行得通。稍早我曾经提过,前一版operator=不仅不具备“自我赋值安全性”,也不具备“异常安全性”,这个新版本仍然存在异常方面的麻烦。比如书中的例子给出这样表述,如果, rhs.buffer;
是一块空地址,或者异常地址,依然会出现以上情况。那么就有了另一种技术保证异常的自我赋值了。
在operator=函数内手工排列语句(确保代码不但“异常安全”而且“自我赋值安全”)的一个替代方案是,使用所谓的copy and swap技术。这个技术和“异常安全性”有密切关系,所以由条款29详细说明。然而由于它是一个常见而够好的operator=撰写办法,所以值得看看其实现手法像什么样子:
// self_opera.cpp : 定义控制台应用程序的入口点。
//2011/9/11-by wallwind-in revenco
#include
"stdafx.h"
#include
<iostream>
#include
<stdlib.h>
#include
<string.h>
using
namespace
std;
class
myclass {
public:
myclass() {
_buffer = new
char[255];
memset(_buffer, 65, sizeof(char) * 255);
}
myclass(const myclass& rhs) {
_buffer = new
char[255];
memcpy(_buffer, rhs._buffer, sizeof(char) * 255);
}
myclass& operator=(const
myclass& rhs) {
myclass temp(rhs);
swap(temp);
return *this;
}
char* ToString() const {
return _buffer; }
private:
void swap(const
myclass& rhs) {
delete[] _buffer;
_buffer = rhs._buffer;
}
char *_buffer;
};
int
_tmain(int
argc, _TCHAR* argv[]) {
myclass str1;
printf("%sn", str1.ToString());
str1 = str1;
printf("%sn", str1.ToString());
system("pause");
return 0;
}
我个人比较忧虑这个做法,我认为它为了伶俐巧妙的修补而牺牲了清晰性。然而将“copy 动作”从函数本体内移至“函数参数构造阶段”却可令编译器有时生成更高效的代码。
请记住:
1、确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。
2、确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
相关文章推荐
- Effective c++学习笔记——条款11:在operateor=中自我赋值
- Effective C++_笔记_条款11_在operator=中处理“自我赋值”
- 【Effective c++】条款11:在operator=中处理“自我赋值”
- 读书笔记《Effective c++》 条款11 在operator= 中处理“自我赋值”
- 《Effective C++》学习笔记条款11 在operator =中处理“自我赋值”
- Effective C++ -----条款11: 在operator=中处理“自我赋值”
- Effective C++ 条款11:在operator=中处理"自我赋值"
- Effective C++ 11 在operator=中处理“自我赋值” 笔记
- Effective C++:条款11:在operator= 中处理“自我赋值”。
- Effective C++ 条款10 令operator=返回一个reference to *this 条款11 在operator=中处理"自我赋值"
- 《Effect C++》学习------条款11:在 operator= 中处理“自我赋值”
- Effective C++ 条款11 在赋值操作符operator=中处理“自我赋值”
- [置顶] 高效effective C++ 55条款之个人学习笔记二
- 条款11 在operator=中处理“自我赋值”
- Effective C++学习笔记——条款03:尽可能使用const
- Effective C++学习笔记:条款2:尽量用<iostream>而不用<stdio.h>
- 条款11 在operator=中处理“自我赋值”
- 条款11: 在operator= 中处理"自我赋值"
- Effective C++学习笔记:条款1:尽量用const和inline而不用#define
- Effective C++ 条款12:复制对象时勿忘其每一个成分 学习笔记