c++下为使用pimpl方法的类编写高效的swap函数
swap函数是c++中一个常用的函数,用于交换两对象的值,此外还用于在重载赋值运算符中处理自赋值情况和进行异常安全性编程(见下篇),标准模板库中swap的典型实现如下:
1 namespace stl 2 { 3 template <typename T> 4 void Swap(T &lhs, T &rhs) 5 { 6 T temp(lhs); 7 lhs = rhs; 8 rhs = temp; 9 } 10 }
缺省版本的Swap函数包含三次对象的复制:lhs复制到temp,rhs复制到lhs,temp复制到rhs。然而在一些情况下,这种复制是无意义的,这会使得缺省版本Swap函数的效率低下,一种典型的情况出现在使用pimpl方法时:
——pimpl全称为pointer to implementation,即一个指针指向的对象包含真正的数据,例如:
1 namespace jz 2 { 3 class PersonImpl 4 { 5 public: 6 PersonImpl(const std::string &name_, uint8_t sex_, uint8_t age_, const std::initializer_list<int> &int_il_) 7 : name(name_), sex(sex_), age(age_) 8 { 9 i_vec.assign(int_il_.begin(), int_il_.end()); 10 } 11 // 12 PersonImpl(const PersonImpl &rhs) 13 : name(rhs.name), sex(rhs.sex), age(rhs.age), i_vec(rhs.i_vec) 14 { 15 16 } 17 // 18 PersonImpl& operator=(const PersonImpl &rhs) 19 { 20 name = rhs.name; 21 sex = rhs.sex; 22 age = rhs.age; 23 i_vec = rhs.i_vec; 24 } 25 private: 26 std::string name; 27 uint8_t sex : 1; //标准没有规定char一定是8-bit,使用8-bit的unsigned int类型,需要包含<cstdint> 28 uint8_t age : 7; //使用位域 29 std::vector<int> i_vec; 30 }; 31 32 class Person 33 { 34 public: 35 Person(const std::string &name_, uint8_t sex_, uint8_t age_, const std::initializer_list<int> &int_il_) 36 : sptr_impl(std::make_shared<PersonImpl>(name_, sex_, age_, int_il_)) 37 { 38 39 } 40 // 41 Person(const Person &rhs) 42 : sptr_impl(std::make_shared<PersonImpl>(*rhs.sptr_impl)) 43 { 44 45 } 46 // 47 Person& operator=(const Person &rhs) 48 { 49 *sptr_impl = *rhs.sptr_impl; 50 } 51 private: 52 std::shared_ptr<PersonImpl> sptr_impl; 53 };
显然若对上面代码中的两个Person对象执行Swap操作,效率很低,因为显然只需将两个对象的智能指针成员sptr_impl进行交换,而缺省版本却会将指针指向的PersonImpl对象进行三次复制。因此,需要为Person特化Swap函数,编写一个高效的版本。
编写方法:
①为对象提供一个public的Swap成员函数,在该函数中高效地置换两个对象的值。
②在定义对象类的命名空间中提供一个非成员版本的Swap函数,调用成员函数版本的Swap函数。
③若编写的是类而不是类模板,在成员函数版本的Swap中特化标准库版本的Swap,根据名称查找法则,会优先调用特化版本的Swap。
代码如下:
1 namespace jz 2 { 3 inline void Person::Swap(Person &rhs) 4 { 5 using stl::Swap; 6 Swap(sptr_impl, rhs.sptr_impl); 7 } 8 9 // 10 void Swap(Person &lhs, Person &rhs) 11 { 12 lhs.Swap(rhs); 13 } 14 }
附注:
swap函数的用处之一就是为类和类模板提供异常安全性保障,但这种保障基于一个假设:成员函数版本的Swap函数不抛出异常(缺省版本的Swap函数使用了拷贝构造函数和拷贝赋值运算符,都有可能抛出异常)。而因为高效率地Swap函数总是基于对内置类型的操作(比如指针),且内置类型上的操作不会抛出异常,所以上面的成员函数版Swap若要保证绝对不抛出异常,应该将智能指针改为内置类型的指针。