Читаем Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ полностью

WidgetImpl *pimpl; // указатель на объект с данными

}; // этого Widget


Чтобы обменять значения двух объектов Widget, нужно лишь обменять значениями их указатели pimpl, но алгоритм swap по умолчанию об этом знать не может. Вместо этого он не только трижды выполнит операцию копирования Widget, но еще и три раза скопирует Widgetlmpl. Очень неэффективно!

А нам бы хотелось сообщить функции std::swap, что при обмене объектов Widget нужно обменять значения хранящихся в них указателей pimpl. И такой способ существует: специализировать std::swap для класса Widget. Ниже приведена основная идея, хотя в таком виде код не скомпилируется:


namespace std {

template // это специализированная версия

void swapWidget(Widget a, // std::swap, когда T есть

Widget b) // Widget; не скомпилируется

{

swap(a.pimpl, b.pimpl); // для обмена двух Widget просто

} // обмениваем их указатели pimpl

}


Строка «template » в начале функции говорит о том, что это полная специализация шаблона std::swap, а «Widget» после имени функции говорит о том, что это специализация для случая, когда T есть Widget. Другими словами, когда общий шаблон swap применяется к Widget, то должна быть использована эта реализация. Вообще-то не допускается изменять содержимое пространства имен std, но разрешено вводить полные специализации стандартных шаблонов (подобных swap) для созданных нами типов (например, Widget). Что мы и делаем.

Как я уже сказал, эта функция не скомпилируется. Дело в том, что она пытается получить доступ к указателям 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 swapWidget(Widget a, // std::swap

Widget b)

{

a.swap(b); // чтобы обменять значениями Widget,

} // вызываем функцию-член swap

}


Этот вариант не только компилируется, но и полностью согласован с STL-контейнерами, каждый из которых предоставляет и открытую функцию-член swap, и специализированную версию std::swap, которая вызывает эту функцию-член.

Предположим, однако, что Widget и Widgetlmpl – это не обычные, а шаблонные классы. Возможно, это понадобилось для того, чтобы можно было параметризировать тип данных, хранимых в Widgetlmpl:


template typename T

class WidgetImpl {...};

template typename T

class Widget {...};


Поместить функцию-член swap в Widget (и при необходимости в Widgetlmpl) в этом случае так же легко, как и раньше, но мы сталкиваемся с проблемой, касающейся специализации std::swap. Вот что мы хотим написать:


namespace std {

template typename T

void swapWidgetT(WidgetT a, // ошибка! Недопустимый код

WidgetT b)

{ a.swap(b);}

}


Выглядит совершенно разумно, но все равно неправильно. Мы пытаемся частично специализировать шаблон функции (std::swap), но, хотя C++ допускает частичную специализацию шаблонов класса, он не разрешает этого для шаблонов функций. Этот код не должен компилироваться (если только некоторые компиляторы не пропустят его по ошибке).

Когда вам нужно «частично специализировать» шаблон функции, лучше просто добавить перегруженную версию. Примерно так:


namespace std {

template typename T

void swap(WidgetT a, // перегрузка std::swap

WidgetT b) // (отметим отсутствие ... после

{ a.swap(b);} // “swap”), далее объяснено, почему

} // этот код некорректен


Перейти на страницу:
Нет соединения с сервером, попробуйте зайти чуть позже