Чтение онлайн

на главную

Жанры

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

Майерс Скотт

Шрифт:

При написании шаблонов выполняется такой же анализ, и способы борьбы с дублированием аналогичны. Однако имеются новые особенности. В нешаблонном коде дублирование видно сразу: трудно не заметить повторения кода в двух функциях или классах. В шаблонном коде дублирование не бросается в глаза: есть только одна копия исходного кода шаблона, поэтому вам нужно тренироваться, чтобы легко находить места, где в результате конкретизации шаблона может возникнуть дублирование.

Предположим, например, что вы хотите написать шаблон для квадратных матриц фиксированного размера, которые, помимо всего прочего, поддерживают операцию обращения матрицы.

template<typename T, std::size_t n> //
шаблон матрицы размерностью n x n,

class SquareMatrix { // состоящей из объектов типа T;

public: // см. ниже информацию о параметре size_t

...

void invert; // обращение матрицы на месте

};

Этот шаблон принимает параметр типа T, а также параметр типа size_t, не являющийся типом. Параметры, не являющиеся типами, используются реже, чем параметры-типы, но они совершенно законны и, как в данном примере, могут быть вполне естественными.

Теперь рассмотрим такой код:

SquareMatrix<double, 5> sm1;

...

sm1.invert; // вызов SquareMatrix<double, 5>::invert

SquareMatrix<double, 10> sm2;

...

sm2.invert; // вызов SquareMatrix<double, 10>::invert

Здесь будут конкретизированы две копии функции invert. Они не идентичны, потому что одна из них работает с матрицами 5x5, а другая – с матрицами 10x10, но во всем остальном, кроме констант 5 и 10, эти функции ничем не отличаются. Это – классический пример разбухания кода в результате применения шаблонов.

Что вы делаете, когда есть две функции, абсолютно одинаковые, за исключением того, что в одной используется константа 5, а в другой – 10? Естественно, вы создаете функцию, которая принимает параметр, а затем вызываете ее, один раз передавая в качестве параметра 5, а другой раз – 10. Вот первая попытка проделать тот же трюк в реализации шаблона SquareMatrix:

template<typename T> // базовый класс, не зависящий

class SquareMatrixBase { // от размерности матрицы

protected:

...

void invert(std::size_t matrixSize); // обратить матрицу заданной

... // размерности

};

template<typename T, std::size_t n>

class SquareMatrix: private SquareMatrixBase<T> {

private:

using SquareMatrixBase<T>::invert; // чтобы избежать сокрытия базовой

// версии invert; см. правило 33

public:

...

void invert {this->invert(n);} // встроенный вызов версии invert

}; // из базового класса

// см. ниже – почему

// применяется “this->”

Как видите, параметризованная версия функции invert

находится в базовом классе – SquareMatrixBase. Как и SquareMatrix, SquareMatrixBase – шаблон, но в отличие от SquareMatrix, он имеет только один параметр – тип объектов в матрице, но не имеет параметра size. Поэтому все матрицы, содержащие объекты заданного типа, будут разделять общий класс SquareMatrixBase. И, значит, все они разделят единственную копию функции invert из данного класса.

Назначение SquareMatrixBase::invert – помочь избежать дублирования кода в производных классах, поэтому using-объявление помещено в секцию protected, а не public. Дополнительные расходы на вызов этой функции нулевые, поскольку в производных классах ее вызовы invert встроены (встраивание неявное – см. правило 30). Во встроенных функциях применяется нотация «this->», потому что в противном случае, как следует из правила 43, имена функций из шаблонного базового класса (SquareMatrixBase<T>) будут скрыты от подклассов. Отметим также, что наследование SquareMatrix от SquareMatrixBase – закрытое. Это отражает тот факт, что базовый класс введен только для одной цели – упростить реализацию производных, и не означает наличия концептуального отношения «является» между SquareMatrixBase и SquareMatrix (о закрытом наследовании см. правило 39).

До сих пор все шло хорошо, но имеется одна проблема, которую нам еще предстоит решить. Откуда класс SquareMatrixBase узнает, с какими данными он должен работать? Размерность матрицы ему известна из параметра, но как узнать, где находятся сами данные конкретной матрицы? По-видимому, это известно только производному классу. А как производный класс может передать эту информацию базовому, чтобы тот мог выполнить обращение матрицы?

Один из возможных способов – добавить дополнительный параметр в функцию SquareMatrixBase::invert, скажем, указатель на начало участка памяти, где размещаются данные матрицы. Это будет работать, но, скорее всего, invert – не единственная функция в классе SquareMatrix, которая может быть написана так, что не будет зависеть от размерности, и перенесена в класс SquareMatrixBase. Если таких функций будет несколько, всем им понадобится знать, где находятся данные матрицы. Нам придется в каждую добавлять новый параметр, и получится, что мы многократно передаем SquareMatrixBase одну и ту же информацию. Как-то неправильно это.

Есть альтернатива – хранить указатель на данные матрицы в SquareMatrixBase. И там же можно хранить размерность матрицы. Получается такой код:

template<typename T>

class SquareMatrixBase {

protected:

SquareMatrixBase(std::size_t n, T pMem) // сохраняет размерность

:size(n), pData(pMem){} // и указатель на данные матрицы

void setData(T *ptr) { pData = ptr;} // присвоить значение pData

...

private:

std::size_t size; // размерность матрицы

T *pData; // указатель на данные матрицы

};

Это позволяет производным классам решать, как выделять память. Возможна, в частности, реализация, при которой данные матрицы сохраняются прямо в объекте SquareMatrix:

template<typename T, size_t size>

class SquareMatrix: private SquareMatrixBase {

Поделиться:
Популярные книги

Бастард Императора

Орлов Андрей Юрьевич
1. Бастард Императора
Фантастика:
фэнтези
аниме
5.00
рейтинг книги
Бастард Императора

На границе империй. Том 10. Часть 1

INDIGO
Вселенная EVE Online
Фантастика:
космическая фантастика
попаданцы
5.00
рейтинг книги
На границе империй. Том 10. Часть 1

Имя нам Легион. Том 7

Дорничев Дмитрий
7. Меж двух миров
Фантастика:
боевая фантастика
рпг
аниме
5.00
рейтинг книги
Имя нам Легион. Том 7

Измена. Вторая жена мужа

Караева Алсу
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Измена. Вторая жена мужа

Буря империи

Сай Ярослав
6. Медорфенов
Фантастика:
аниме
фэнтези
фантастика: прочее
эпическая фантастика
5.00
рейтинг книги
Буря империи

Пенсия для морского дьявола

Чиркунов Игорь
1. Первый в касте бездны
Фантастика:
попаданцы
5.29
рейтинг книги
Пенсия для морского дьявола

На изломе чувств

Юнина Наталья
Любовные романы:
современные любовные романы
6.83
рейтинг книги
На изломе чувств

Тринадцатый II

NikL
2. Видящий смерть
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Тринадцатый II

Сирота

Шмаков Алексей Семенович
1. Светлая Тьма
Фантастика:
юмористическое фэнтези
городское фэнтези
аниме
5.00
рейтинг книги
Сирота

Законы Рода. Том 9

Flow Ascold
9. Граф Берестьев
Фантастика:
городское фэнтези
попаданцы
аниме
дорама
фэнтези
фантастика: прочее
5.00
рейтинг книги
Законы Рода. Том 9

Красноармеец

Поселягин Владимир Геннадьевич
1. Красноармеец
Фантастика:
боевая фантастика
попаданцы
4.60
рейтинг книги
Красноармеец

Огненный князь 4

Машуков Тимур
4. Багряный восход
Фантастика:
попаданцы
аниме
5.00
рейтинг книги
Огненный князь 4

Начальник милиции. Книга 5

Дамиров Рафаэль
5. Начальник милиции
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Начальник милиции. Книга 5

Инкарнатор

Прокофьев Роман Юрьевич
1. Стеллар
Фантастика:
боевая фантастика
рпг
7.30
рейтинг книги
Инкарнатор