Почему шаблон Decorator работает с указателями, а не со ссылками? - PullRequest
3 голосов
/ 31 марта 2020

Я только что узнал о шаблоне декоратора и попытался написать пример, использующий код. Пример о напитках и некоторых приправах. Внутри Decorator у меня есть эталонная переменная для напитка. Доступны следующие напитки: Decaf и Espresso. Доступны следующие приправы: Soy и Caramel. Например, если я определю Decaf с более чем одним Caramel, то получу результат - просто Decaf с одним декоратором. Так что определите Caramel -> Caramel -> Decaf, что дает мне Caramel -> Decaf. Определение Caramel -> Soy -> Caramel -> Decaf работает нормально. Определение Caramel -> Soy -> Caramel -> Caramel -> Decaf дает мне Caramel -> Soy -> Caramel -> Decaf. Короче говоря, у меня не может быть двух или более приправ одного и того же типа один за другим. Они становятся только одной приправой. Если я использую указатели, он работает нормально.

Код:

#include <iostream>
//#include "Decaf.h"
//#include "Espresso.h"
//#include "SoyDecorator.h"
//#include "CaramelDecorator.h"

class Beverage
{
public:

    virtual std::string GetDescription() const = 0;
    virtual int GetCost() const = 0;
};

class CondimentDecorator : public Beverage
{
public:

    Beverage& beverage;
    CondimentDecorator(Beverage& beverage) : beverage(beverage) {}
};

class Espresso : public Beverage
{
    virtual std::string GetDescription() const override
    {
        return "Espresso";
    }

    virtual int GetCost() const override
    {
        return 5;
    }
};

class Decaf : public Beverage
{
    virtual std::string GetDescription() const override
    {
        return "Decaf";
    }

    virtual int GetCost() const override
    {
        return 4;
    }
};

class CaramelDecorator : public CondimentDecorator
{
public:

    CaramelDecorator(Beverage& beverage) : CondimentDecorator(beverage) {}

    virtual std::string GetDescription() const override
    {
        return this->beverage.GetDescription() + " with Caramel";
    }

    virtual int GetCost() const override
    {
        return this->beverage.GetCost() + 2;
    }
};

class SoyDecorator : public CondimentDecorator
{
public:

    SoyDecorator(Beverage& beverage) : CondimentDecorator(beverage) {}

    virtual std::string GetDescription() const override
    {
        return this->beverage.GetDescription() + " with Soy";
    }

    virtual int GetCost() const override
    {
        return this->beverage.GetCost() + 1;
    }
};

int main()
{
    Decaf d;
    SoyDecorator s(d);
    CaramelDecorator c(s);
    CaramelDecorator cc(c);

    std::cout << cc.GetDescription() << std::endl;
    std::cout << cc.GetCost() << std::endl;
}

output:

Decaf with Soy with Caramel
7

// Expected:
// Decaf with Soy with Caramel with Caramel
// 9

Вот тот же код, но с использованием указателей и работает просто отлично: https://ideone.com/7fpGSp

1 Ответ

2 голосов
/ 31 марта 2020

При переключении с указателей на ссылки сигнатура конструктора OP становится очень похожей на конструктор копирования (по умолчанию).

    CondimentDecorator(Beverage &beverage) : beverage(beverage) {}

против

    CondimentDecorator(const Beverage&); // generated by compiler

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

Наконец, я смог решить проблему OP с предоставлением соотв. кандидаты, которые не позволяют использовать конструктор копирования.

(Удаление конструктора копирования больше не требовалось, но я оставил его.)

class CondimentDecorator : public Beverage
{
public:

    Beverage& beverage;
    CondimentDecorator(Beverage &beverage) : beverage(beverage) {}
    CondimentDecorator(CondimentDecorator &beverage) : beverage(beverage) {}
    CondimentDecorator(const CondimentDecorator&) = delete;
};

То же самое должно быть сделано для производных классов :

class CaramelDecorator : public CondimentDecorator
{
public:

    CaramelDecorator(Beverage &beverage) : CondimentDecorator(beverage) {}
    CaramelDecorator(CaramelDecorator &beverage) : CondimentDecorator(beverage) {}
    //CaramelDecorator(const CaramelDecorator&) = delete;

    virtual std::string GetDescription() const override
    {
        return this->beverage.GetDescription() + " with Caramel";
    }

    virtual int GetCost() const override
    {
        return this->beverage.GetCost() + 2;
    }
};

Я исправил только CaramelDecorator для демонстрации, но на самом деле это должно быть сделано для всех производных классов class CondimentDecorator.

Фиксированный MCVE для OP:

#include <iostream>
//#include "Decaf.h"
//#include "Espresso.h"
//#include "SoyDecorator.h"
//#include "CaramelDecorator.h"

class Beverage
{
public:
    virtual std::string GetDescription() const = 0;
    virtual int GetCost() const = 0;
};

class CondimentDecorator : public Beverage
{
public:

    Beverage& beverage;
    CondimentDecorator(Beverage &beverage) : beverage(beverage) {}
    CondimentDecorator(CondimentDecorator &beverage) : beverage(beverage) {}
    CondimentDecorator(const CondimentDecorator&) = delete;
};

class Espresso : public Beverage
{
    virtual std::string GetDescription() const override
    {
        return "Espresso";
    }

    virtual int GetCost() const override
    {
        return 5;
    }
};

class Decaf : public Beverage
{
    virtual std::string GetDescription() const override
    {
        return "Decaf";
    }

    virtual int GetCost() const override
    {
        return 4;
    }
};

class CaramelDecorator : public CondimentDecorator
{
public:

    CaramelDecorator(Beverage &beverage) : CondimentDecorator(beverage) {}
    CaramelDecorator(CaramelDecorator &beverage) : CondimentDecorator(beverage) {}
    //CaramelDecorator(const CaramelDecorator&) = delete;

    virtual std::string GetDescription() const override
    {
        return this->beverage.GetDescription() + " with Caramel";
    }

    virtual int GetCost() const override
    {
        return this->beverage.GetCost() + 2;
    }
};

class SoyDecorator : public CondimentDecorator
{
public:

    SoyDecorator(Beverage &beverage) : CondimentDecorator(beverage) {}

    virtual std::string GetDescription() const override
    {
        return this->beverage.GetDescription() + " with Soy";
    }

    virtual int GetCost() const override
    {
        return this->beverage.GetCost() + 1;
    }
};

int main()
{
    Decaf d;
    SoyDecorator s(d);
    CaramelDecorator c(s);
    CaramelDecorator cc(c);

    std::cout << cc.GetDescription() << std::endl;
    std::cout << cc.GetCost() << std::endl;
}

Выход:

Decaf with Soy with Caramel with Caramel
9

Live демо на coliru


Почему дополнительные нужны кандидаты?

CondimentDecorator является производным от Beverage.

Итак, для:

CondimentDecorator d;
CondimentDecorator d2(d);

у компилятора есть два варианта построения d2:

  1. пользовательский конструктор CondimentDecorator::CondimentDecorator(Beverage &beverage)
  2. (по умолчанию) конструктор копирования CondimentDecorator::CondimentDecorator(const CondimentDecorator&).

Для первого - неявное приведение должен быть применен, но для конструктора копирования нет необходимости в преобразовании (или, самое большее, в преобразование const).

Следовательно, компилятор предпочитает конструктор копирования (к сожалению, даже, хотя он удален).

Итак, необходимо предоставить другого кандидата, который требует не менее явных приведений, таких как конструктор копирования:

другой пользовательский конструктор CondimentDecorator::CondimentDecorator(CondimentDecorator&).

Дополнительная информация: Разрешение перегрузки

...