我的第一本C++书

游历C++世界的地图

C++小品:吃火锅与shared_ptr,指针,拷贝构造函数和delete

原文来自

http://imcc.blogbus.com/

C++小品:吃火锅与shared_ptr,指针,拷贝构造函数和delete

读者Terry问到一个关于拷贝构造函数的问题,大家可以参考答Terry:拷贝构造函数,其中论述了拷贝构造函数的必要性,然而,任何事物都是具有两面性的,有时候我们需要自己定义类的拷贝构造函数来完成类的拷贝,然后,有的时候,这种以拷贝一个对象来完成另外一个对象的创建是不合理的(也就是在现实世界中,这种对象没有可复制性,例如,人民币),是应该被禁止的。我们来举一个吃火锅的例子:

// 火锅,可以从中取出其中烫的东西
class hotpot
{
public:
hotpot(string f) : food(f)
{
}
string fetch()
{
return food;
}
private:
string food;
};

// 吃火锅用的碗,当然是每个人专用的
class bowl
{
public:
bowl(string o) : owner(o)
{
}
void put(string food)
{
cout<<"put "< }

private:
string owner;
};
// 吃火锅的人
class human
{
public:
// 名子和吃的火锅
human(string n,shared_ptr ppot) : name(n),pot(ppot)
{
pbowl = new bowl(name);
};
// OK了,从火锅中取出来放到自己的碗里
void fetch()
{
string food = pot->fetch();
// 放到自己的碗里
coutput(food);
}

private:
string name;
shared_ptr pot;
bowl* pbowl;
};
int main()
{
// 服务员端上来牛肉火锅
shared_ptr fpot(new hotpot("beaf"));
// terry入席
human terry("terry",fpot);
// 又来了一个姓陈的,这里用的是默认的拷贝构造函数来创建terry的副本
human chen = terry;
// terry夹了一块肉
terry.fetch();
// 陈先生也夹了一块肉
chen.fetch();

return 0;
}

到这里,似乎看起来一切OK,然而从程序输出中我们却发现了问题:

terry put beaf into terry's bowl.
terry put beaf into terry's bowl.

O my god!明明是两个人(terry和chen),但是好像却只有一个人做了两次,陈先生也把肉加到了terry的碗里。

这就是当类中有指针类型的数据成员时,使用默认的拷贝构造函数所带来的问题,导致其中的某些指针成员没有被合理地初始化,这别是当这些指针指向的是与这个对象(human)有所属关系的资源(bowl),在这种时候,我们必须自己定义类的拷贝构造函数,完成指针成员的合理初始化。在human中添加一个拷贝构造函数

human(const human& h)
{
// 两个人显然不能同名,所以只好给个无名氏了
name = "unknown";
// 使用不同的碗
// bowl和human有所属关系,所以这里必须创建新的对象
pbowl = new bowl(name);
// 不过可以吃同一个火锅
// pot和human并没有所属关系,所以可以共享一个对象
pot = h.pot;
};

添加拷贝构造构造函数之后,两个人不会将东西放到同一个碗中了,自己取得东西不会放到别人的碗里:

terry put beaf into terry's bowl.
unknown put beaf into unknown's bowl.

这样修改好多了,至少两个人不会用同一个碗了。然而,这样还是有问题,我们无法给第二个人命名,他成了无名氏了,这就是类当中的那些没有可复制性的数据成员(一个人的名字自然不可以复制给另外一个人,如果human中有个wife,那肯定要上演世界大战了),拷贝构造函数就会产生这样的问题。

实际上,对于这类不具备可复制性的对象,为了不引起混乱,其拷贝构造操作是应当被禁止的,新标准C++11就注意到了这个问题,提供了一个delete关键字来禁用某些可能存在的(即使你规定human不可复制,也无法阻止程序员在使用human时写出human chen = terry这样的不合理的代码)默认的(类的拷贝构造函数是默认提供的,对于那些不具备可复制性的类来说,这简直是画蛇添足,好心办了坏事情)不合理的操作,这样,我们就不能使用拷贝 构造函数了:

// 禁用human的拷贝构造函数
human(const human& h) = delete;

经过这样的定义,当我们在代码中尝试将一个对象复制给另外一个对象(会调用拷贝构造函数)时,编译器就会出错误提示,提醒程序员:hi,这样可不行,我是独一无二的,不能够被复制

human chen = terry;

编译器给这样的提示:
Noname1.cpp:41:2: error: deleted function 'human::human(const human&)'
Noname1.cpp:59:15: error: used here

所以,总结起来,在使用拷贝构造函数时,有两个需要注意的地方:

  • 如果类当中有指向具有所属关系的对象的指针时(human中的pbowl指向的是属于human的bowl对象,每个human对象应该有专属的bowl对象),我们必须自定义拷贝构造函数,为这个指针创建属于自己的专属对象。
  • 如果这个类当中,有不具备可复制性的成员(例如name,rmb,wife等),为了防止对象被错误的复制(即使我们没有定义拷贝构造函数,编译器也会默认提供,真是多此一举),我们必须用delete禁用拷贝构造函数,这样才能保证对象不会被错误地复制。关于human的克隆技术,应当是被明令禁止(delete)的。

posted on 2011-09-10 16:10 陈良乔——《我的第一本C++书》 阅读(1929) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理