今天有朋友在成都程序员QQ群里提出一个的问题:在一个类的拷贝构造函数里,是否可以调用重载后的赋值运算操作符,以便用一套代码来方便地实现对类成员的赋值处理。
00001: class Foo {
00002: public:
00003: Foo(const Foo & sour) {
00004: *this = sour;
00005: }
00006: Foo & operator = (const Foo & rv) {
00007: // do something
00008: }
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: }
除了代码啰嗦,似乎也找不出更多的理由来反对这种做法(在拷贝构造中调用重载后的赋值运算符),但是从情理上似乎说不通,但要证明这种做法的错误,就必须举出一个很明显的反例,该反例能说明在某种情况下,这样调用必然会出问题。
可惜,我还没有想出一个例子,能对这种违反语义、但不违反语法,同时能够达到目的的做法进行 证伪。