2006年12月21日

在STL中,for_each函数的第1、2个参数指定操作范围,第3个参数是一个函数对象,用于对范围中的每个由迭代器提供的对象进行操作,它通常是一个具有单个参数的函数的函数指针,或者是一个重载了括号运算符的类实例。

利用<functional>头文件中的mem_fun模板函数,可以返回mem_fun_t这个模板类的实例,从而使得迭代器提供对象的成员函数能够被for_each所用。但是,在实际应用中,往往需要让for_each可以调用任意一个类的成员函数,但STL好象并未提供,只好自己写一个:

00001: template<class C, typename R, typename T>00002: class MemberUnaryFuncT00003:     : public std::unary_function<T, R>00004: {00005: public:00006:     typedef R (C::*METHOD)(T);00007:     explicit MemberUnaryFuncT(C * instanceOfC, METHOD method)00008:         : instanceOfC_(instanceOfC), method_(method)00009:     {}00010:     inline R operator () (T t) const { return (instanceOfC_->*method_)(t); }00011: private:00012:     C       * instanceOfC_;00013:     METHOD  method_;00014: };00015: 00016: template<class C, typename R, typename T>00017: class MemberUnaryConstFuncT00018:     : public std::unary_function<T, R>00019: {00020: public:00021:     typedef R (C::*METHOD)(T) const;00022:     explicit MemberUnaryConstFuncT(C * instanceOfC, METHOD method)00023:         : instanceOfC_(instanceOfC), method_(method)00024:     {}00025:     inline R operator () (T t) const { return (instanceOfC_->*method_)(t); }00026: private:00027:     C       * instanceOfC_;00028:     METHOD  method_;00029: };00030: 00031: template<class C, typename R, typename T>00032: inline00033: MemberUnaryFuncT<C,R,T> MemberFunc(C * instanceOfC, R (C::*method)(T)) {00034:     return MemberUnaryFuncT<C,R,T>(instanceOfC, method);00035: }00036: 00037: template<class C, typename R, typename T>00038: inline00039: MemberUnaryConstFuncT<C,R,T> MemberFunc(C * instanceOfC, R (C::*method)(T) const) {00040:     return MemberUnaryConstFuncT<C,R,T>(instanceOfC, method);00041: }

使用示例

00001: class Foo {00002:     void test1(int) {}00003:     void test2(int) {}00004: public:00005:     Foo() {00006:         std::vector<int> container;00007:         std::for_each(container.begin(), container.end(), MemberFunc(this, test1));00008:         std::for_each(container.begin(), container.end(), MemberFunc(this, test2));00009:     }00010: }; 
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:     }

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

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