Двойная отправка и фабричный образец

У меня есть следующий код в настоящее время (не работает):

#include <iostream>
#include <vector>

class Circle;
class Rectangle;

class Shape {
private:
    Shape() {};
public:
    virtual ~Shape() {};
    friend class Circle;
    friend class Rectangle;
};

class Creator {
public:
    virtual ~Creator() {};
    virtual Shape* create() = 0;
    virtual bool equals(Shape& s) { return false; };
};

class Circle : public Shape {
private:
    Circle() : Shape() {};
public:
    class CircleCreator : public Creator {
    public:
        virtual Shape* create() { return new Circle(); };
        virtual bool equals(Shape& other_shape) { return false; };
    };
};

class Rectangle : public Shape {
private:
    Rectangle() : Shape() {};
public:
    class RectangleCreator : public Creator {
    public:
        virtual Shape* create() { return new Rectangle(); };
        virtual bool equals(Shape& other_shape) { return false; };
    };
};

int main() {
    /* First step, build the list */
    std::vector<Shape*> shapeList;
    std::vector<Shape*>::iterator it;
    Rectangle::RectangleCreator rc;
    Circle::CircleCreator cc;
    Shape* s = cc.create();
    Shape* s1 = rc.create();
    shapeList.push_back(s);
    shapeList.push_back(s1);

    /* Second step: check if we've got a shape starting from a creator */
    for (it = shapeList.begin(); it != shapeList.end(); ++it) {
        if (rc.equals(**it)) {
            std::cout << "same shape" << std::endl;
        }
    }
    return 0;
}

Моя цель - использовать фабричный шаблон и избежать создания нового объекта, если в списке у меня уже есть этот объект. Я пытался использовать шаблон двойной отправки, но в этом случае его нелегко применить. Как я могу сделать?

Изменить: так как код используется в "критическом" пути, я хочу избежать RTTI, как dynamic_cast и так далее.

2 ответа

Решение

Может быть, что-то подобное может сделать это с помощью переменных-членов

#include <iostream>
#include <vector>

enum
{
CIRCLE,
RECTANGLE
};

class Circle;
class Rectangle;

class Shape {
private:
    Shape() {};
public:
    unsigned shapeType;
    virtual ~Shape() {};
    friend class Circle;
    friend class Rectangle;
};

class Creator {
public:
unsigned shapeType;
    virtual ~Creator() {};
    virtual Shape* create() = 0;
    bool equals(Shape& s) { return (this->shapeType == s.shapeType); };
};

class Circle : public Shape {
private:
    Circle() : Shape() {shapeType=CIRCLE;};
public:
    class CircleCreator : public Creator {
    public:
        CircleCreator() {shapeType=CIRCLE;};
        virtual Shape* create() { return new Circle(); };
    };
};

class Rectangle : public Shape {
private:
    Rectangle() : Shape() {shapeType=RECTANGLE;};
public:
    class RectangleCreator : public Creator {
    public:
        RectangleCreator() {shapeType=RECTANGLE;};
        virtual Shape* create() { return new Rectangle(); };
    };
};

int main() {
    /* First step, build the list */
    std::vector<Shape*> shapeList;
    std::vector<Shape*>::iterator it;
    Rectangle::RectangleCreator rc;
    Circle::CircleCreator cc;
    Shape* s = cc.create();
    Shape* s1 = rc.create();
    shapeList.push_back(s);
    shapeList.push_back(s1);

    /* Second step: check if we've got a shape starting from a creator */
    for (it = shapeList.begin(); it != shapeList.end(); ++it) {
        if (rc.equals(**it)) {
            std::cout << "same shape" << std::endl;
        }
    }
    return 0;
}

или это - использование виртуальной функции для возврата типа

#include <iostream>
#include <vector>

enum
{
    CIRCLE,
RECTANGLE,
UNKNOWN
};
class Circle;
class Rectangle;

class Shape {
private:
    Shape() {};
public:
    virtual ~Shape() {};
    friend class Circle;
    friend class Rectangle;
    virtual unsigned iAmA(){return UNKNOWN;};
};

class Creator {
public:
    virtual ~Creator() {};
    virtual Shape* create() = 0;
    virtual bool equals(Shape& s) { return false; };
};

class Circle : public Shape {
private:
    Circle() : Shape() {};
    virtual unsigned iAmA(){return CIRCLE;};
public:
    class CircleCreator : public Creator {
    public:
        CircleCreator() {};
        virtual Shape* create() { return new Circle(); };
        virtual bool equals(Shape& other_shape) { return (CIRCLE == other_shape.iAmA()); };
    };
};

class Rectangle : public Shape {
private:
    Rectangle() : Shape() {};
    virtual unsigned iAmA(){return RECTANGLE;};
public:
    class RectangleCreator : public Creator {
    public:
        RectangleCreator() {};
        virtual Shape* create() { return new Rectangle(); };
        virtual bool equals(Shape& other_shape) { return (RECTANGLE == other_shape.iAmA()); };
    };
};

int main() {
    /* First step, build the list */
    std::vector<Shape*> shapeList;
    std::vector<Shape*>::iterator it;
    Rectangle::RectangleCreator rc;
    Circle::CircleCreator cc;
    Shape* s = cc.create();
    Shape* s1 = rc.create();
    shapeList.push_back(s);
    shapeList.push_back(s1);

    /* Second step: check if we've got a shape starting from a creator */
    for (it = shapeList.begin(); it != shapeList.end(); ++it) {
        if (rc.equals(**it)) {
            std::cout << "same shape" << std::endl;
        }
    }
    return 0;
}

Я не уверен, что вы пытаетесь сделать, но я думаю, что это может указать вам направление

enum class Shapes
{
    Rectangle,
    Circle,
    ...
};

class Shape
{
private:
    Shapes m_shape;

protected:
    Shape(Shapes shape)
    {
        m_shape = shape;
    }

public:
    Shapes GetShape() { return m_shape; } // this is used to check whether two shapes are equal

    virtual ~Shape() = default;
};

А теперь для фабричного образца вы должны сделать:

class ShapeFactory
{
public:
    static Shape* CreateShape(Shapes shape)
    {
        switch (shape)
        {
            case Shapes::Circle:
                return new Circle();
            // etc.
        }
    }
};

Это кажется мне излишним и не очень умным для меня. Кроме того, это может поместить много кода в одном месте.

Для рассылки вы могли бы это сделать (я полагаю, я на самом деле не фанат этой концепции, поскольку ее можно сделать менее многословной с помощью простого использования шаблона)

class ShapeCreator
{
public:
    virtual Shape* Create() = 0;
    virtual ~ShapeCreator() = default;
};

class Circle : public Shape
{
public:
    class Creator : ShapeCreator
    {
    public:
        Shape* Create() { return new Circle(); }
    };

    Circle() : Shape(Shapes::Circle)
    {}
};

bool SomethingWithCircle()
{
    Circle::Creator circleCreator;
    Shape* first = circleCreator.Create();
    Shape* second = circleCreator.Create();

    // notice memleak here
    return first->GetShape() == second->GetShape();
}

Если вы используете C++11, вы можете пойти еще дальше и избежать всей идеи / которая в любом случае кажется мне очень похожей на java / используя надлежащие методы мастурбации шаблонов. (Все еще может быть применено к pre-C++11, вы просто не сможете указать параметры.)

template<class T>
class ShapeCreator
{
public:
    template<class... TParams>
    static T* Create(TParams&&... parameters) { return new T(std::forward<TParams>(parameters)...); }
};

class Rectangle : public Shape
{
private:
    int m_width;
    int m_height;

public:
    Rectangle(int width, int height) : Shape(Shapes::Rectangle)
    {
        m_width = width;
        m_height = height;
    }
};

bool DoSomethingWithRectangles()
{
    Rectangle* first  = ShapeCreator<Rectangle>::Create(10, 15);
    Shape* second     = ShapeCreator<Rectangle>::Create(20, 25);

    // notice memleak here
    return first->GetShape() == second->GetShape();
}

TL; DR
Вам действительно не нужен RTTI, но вам нужно хранить информацию о типе где-то в базовом типе. Я использую enum Shapes за это.
Как Factory, так и Dispatch могут показаться хорошей идеей, но вам все равно понадобится динамическое приведение куда-то при их использовании.
Вы можете заменить эти два шаблона с помощью шаблонов, но как только вы получите вектор базовых объектов, вам все равно придется dynamic_cast в какой-то момент.
Я не измерял это вообще, но я действительно заинтересован в сравнении производительности использования виртуальных функций и динамического приведения, поскольку я думаю, что они будут очень похожи...

Конечное примечание:
Пожалуйста, обратите внимание, что я лично чувствую, что используя такие методы, как equals или же operator== на классах определение базового интерфейса не очень разумно, так как есть два возможных результата:

  1. equals виртуально -> медленно, но приемлемо
  2. equals не является виртуальным -> не может использоваться в унаследованных типах для фактического выполнения более сложного / релевантного сравнения, нарушая идею Open to extension, closed for modification

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

Вообще говоря, вы можете прийти к точке, в которой вы спросите себя: "почему нет базового объекта и отражения, как в java или C# в C++? Это позволило бы мне использовать все эти красивые и умные шаблоны". Ответ - шаблоны. Зачем это во время выполнения, когда вы можете сделать это во время компиляции?

Другие вопросы по тегам