class WidgetImpl { // класс для данных Widget
public: // детали несущественны
...
private:
int a,b,c; // возможно, много данных –
std::vector
...
};
class Widget { // класс, использующий идиому pimpl
public:
Widget(const Widget& rhs);
Widget& operator=(const Widget& rhs) // чтоб скопировать Widget, копируем
{ // его объект WidgetImpl. Детали
... // реализации operator= как такового
*pimpl = *(rhs.pimpl); // см. в правилах 10, 11 и 12
...
}
...
private:
WidgetImpl *pimpl; // указатель на объект с данными
}; // этого Widget
Чтобы обменять значения двух объектов Widget, нужно лишь обменять значениями их указатели pimpl, но алгоритм swap по умолчанию об этом знать не может. Вместо этого он не только трижды выполнит операцию копирования Widget, но еще и три раза скопирует Widgetlmpl. Очень неэффективно!
А нам бы хотелось сообщить функции std::swap, что при обмене объектов Widget нужно обменять значения хранящихся в них указателей pimpl. И такой способ существует: специализировать std::swap для класса Widget. Ниже приведена основная идея, хотя в таком виде код не скомпилируется:
namespace std {
template <> // это специализированная версия
void swap
Widget& b) // Widget;
{
swap(a.pimpl, b.pimpl); // для обмена двух Widget просто
} // обмениваем их указатели pimpl
}
Строка «template <>» в начале функции говорит о том, что это
Как я уже сказал, эта функция не скомпилируется. Дело в том, что она пытается получить доступ к указателям pimpl внутри a и b, а они закрыты. Мы можем объявить нашу специализацию другом класса, но соглашение требует поступить иначе: нужно объявить в классе Widget открытую функцию-член по имени swap, которая осуществит реальный обмен значениями, а затем специализация std::swap вызовет эту функцию-член:
class Widget { // все как раньше, за исключением
public: // добавления функции-члена swap
...
void swap(Widget& other)
{
using std::swap; // необходимость в этом объявлении
// объясняется далее
swap(pimpl, other.pimpl); // чтобы обменять значениями два объекта
} // Widget,обмениваем указатели pimpl
...
};
namespace std {
template <> // переделанная версия
void swap
Widget& b)
{
a.swap(b); // чтобы обменять значениями Widget,
} // вызываем функцию-член swap
}
Этот вариант не только компилируется, но и полностью согласован с STL-контейнерами, каждый из которых предоставляет и открытую функцию-член swap, и специализированную версию std::swap, которая вызывает эту функцию-член.
Предположим, однако, что Widget и Widgetlmpl – это не обычные, а
template
class WidgetImpl {...};
template
class Widget {...};