2006年12月04日

今天有朋友在成都程序员QQ群里提出一个的问题:在一个类的拷贝构造函数里,是否可以调用重载后的赋值运算操作符,以便用一套代码来方便地实现对类成员的赋值处理。

00001: class Foo {00002: public:00003:     Foo(const Foo & sour) {00004:         *this = sour;00005:     }00006:     Foo & operator = (const Foo & rv) {00007:         // do something00008:     }00009: }; 

拷贝构造和赋值运算在语义上是完全不同的两件事,前者是“基于一个已存在的对象实例构造一个新的对象实例”,后者是“基于一个已存在的对象实例来更新另一个已存在的对象实例”。但是,除了语义上的不同,在语法上是否有问题呢?在逻辑上有没有问题呢?

语法肯定没有问题,上述代码可以顺利通过编译。

从逻辑上讲,似乎也看不出有什么问题。

进一步再想,在什么情况下必须自己来实现拷贝构造和赋值运算符呢?通常,是因为类成员中有指针,指向一些堆上的资源,不希望在复制和赋值的时候,出现两份指针指向同一份资源,比如:

00001: class Foo {00002:     char * data_;00003: public:00004:     Foo() : data_(0) {}00005:     Foo(const Foo & sour) : data_(0) {00006:         *this = sour;00007:     }00008:     Foo & operator = (const Foo & rv) {00009:         if (&rv!=this) {00010:             if (0==data_)00011:                 data_ = new char[DATA_SIZE];00012:             memcpy(data_, rv.data_, DATA_SIZE);00013:         }00014:         return *this;00015:     }00016:     ~Foo() {00017:         delete [] data_;00018:     }00019: }; 

上面的代码的确是啰嗦了,如果拷贝构造函数写成下面这样,明显要清爽一些:

00001:     Foo(const Foo & sour)00002:         : data_(new char[DATA_SIZE])00003:     {00004:         memcpy(data_, sour.data_, DATA_SIZE);00005:     }

除了代码啰嗦,似乎也找不出更多的理由来反对这种做法(在拷贝构造中调用重载后的赋值运算符),但是从情理上似乎说不通,但要证明这种做法的错误,就必须举出一个很明显的反例,该反例能说明在某种情况下,这样调用必然会出问题。

可惜,我还没有想出一个例子,能对这种违反语义、但不违反语法,同时能够达到目的的做法进行证伪

2006年12月01日

CSND的博客慢得恼火,坚持了几个月,实在是无法忍受了,搬家搬家!

以前的BLOG