На следующем этапе переопределим функцию add_item()
так, чтобы она получала объект класса Quote
вместо указателя shared_ptr
. Эта новая версия функции add_item()
отработает резервирование памяти так, чтобы пользователи больше не должны были делать это сами. Определим две ее версии: одна будет копировать переданный ей объект, а другая перемещать его (см. раздел 13.6.3):
void add_item(const Quote& sale); //
void add_item(Quote&& sale); //
Единственная проблема в том, что функция add_item()
не знает, какой тип резервировать. При резервировании памяти функция add_item()
скопирует (или переместит) свой параметр sale
. Выражение new
будет выглядеть примерно так:
new Quote(sale)
К сожалению, это выражение будет неправильным: оператор new
резервирует объект запрошенного типа. Оно резервирует объект типа Quote
и копирует часть Quote
параметра sale
. Но если переданный параметру sale
объект будет иметь тип Bulk_quote
, то он будет усечен.
Эту проблему можно решить, снабдив класс Quote
виртуальной функцией-членом, резервирующей его копию.
class Quote {
public:
//
//
virtual Quote* clone() const & {return new Quote(*this);}
virtual Quote* clone() &&
{return new Quote(std::move(*this));}
//
};
class Bulk_quote : public Quote {
Bulk_quote* clone() const & {return new Bulk_quote(*this);}
Bulk_quote* clone() &&
{return new Bulk_quote(std::move(*this));}
//
};
Поскольку функция add_item()
имеет версии копирования и перемещения, были определены версии l- и r-значения функции clone()
(см. раздел 13.6.3). Каждая функция clone()
резервирует новый объект ее собственного типа. Функция-член константной ссылки на l-значение копирует себя во вновь зарезервированный объект; функция-член ссылки на r-значение перемещает свои данные.
Используя функцию clone()
, довольно просто написать новые версии функции add_item()
:
class Basket {
public:
void add_item(const Quote& sale) //
{ items.insert(std::shared_ptr
(sale.clone())); }
void add_item(Quote&& sale) //
{ items.insert(
std::shared_ptr
(std::move(sale).clone())); }
//
};
Как и сама функция add_item()
, функция clone()
перегружается на основании того, вызвана ли она для l- или r-значения. Таким образом, первая версия функции add_item()
вызывает константную версию l-значения функции clone()
, а вторая версия вызывает версию ссылки на r-значение. Обратите внимание, что хотя в версии r-значения типом параметра sale
является ссылка на r-значение, сам параметр sale
(как и любая другая переменная) является l-значением (см. раздел 13.6.1). Поэтому для привязки ссылки на r-значение к параметру sale
вызывается функция move()
.
Наша функция clone()
является также виртуальной. Будет ли выполнена функция из класса Quote
или Bulk_quote
, зависит (как обычно) от динамического типа параметра sale
. Независимо от того, копируются или перемещаются данные, функция clone()
возвращает указатель на вновь зарезервированный объект его собственного типа. С этим объектом связывается указатель shared_ptr
, и вызывается функция insert()
для добавления этого вновь зарезервированного объекта к items
. Обратите внимание: так как указатель shared_ptr
поддерживает преобразование производного класса в базовый (см. раздел 15.2.2), указатель shared_ptr
можно привязать к Bulk_quote*
.
Упражнение 15.30. Напишите собственную версию класса Basket
и используйте ее для вычисления цены за те же транзакции, что и в предыдущих упражнениях.
15.9. Возвращаясь к запросам текста