C необходимо разрешить переменным-членам класса обращаться к std::vector экземпляров другого класса

#c #class

#c #класс

Вопрос:

Я работаю над API, который должен позволять вам рисовать простые геометрические фигуры и вычислять их элементы. Проект основан на библиотеке SFML.

У меня есть этот класс:

 #ifndef POINT_H_INCLUDED
#define POINT_H_INCLUDED

#include "stdafx.h"
#include "Helper.h"

class Point : public AbstractShape
{
public:
    Point(float x, float y);

    Vector getCoords();
    sf::VertexArray getShape();
    void setLabel(std::string label, int param);
private:
    float m_x, m_y, m_R;
    std::string m_label;
    sf::VertexArray m_shape;
    sf::Text m_labelObject;
};

#endif
  

Он наследуется от абстрактного класса AbstractShape , как и другие подобные классы Segment и Triangle . Мне это нужно, чтобы иметь возможность добавлять разные формы в один контейнер, чтобы потом удобно обрабатывать их в одном месте.

В main функции я объявляю контейнер, затем создаю экземпляр Point , а затем push_back его в контейнер:

 std::vector<AbstractShape*> shapes;
Point* p1 = new Point(100, 50);
p1->setLabel("A", 4);
shapes.push_back(p1);
  

Я думаю, было бы лучше, если бы экземпляр мог добавлять себя в контейнер при создании экземпляра. Для этого Point класс должен иметь возможность видеть контейнер изнутри самого себя. Каков наилучший способ добиться этого, не вводя слишком много связей с Point классом?

Ответ №1:

Чтобы добавить себя в контейнер, Point он должен быть связан с контейнером. Объединение этих двух кажется плохой идеей: зачем Point им что-то знать std::vector ?

Если вы часто используете этот шаблон в своем коде, лучше определить функцию для создания экземпляра и добавления точки в контейнер:

 template<typename T=std::vector<Point*>>
Point *makePoint(int x, int y, T container) {
  Point *p = new Point(x, y);
  container.push_back(p);
  return p; // so we can set other properties easily
}
  

Или создать другой Context класс, который инкапсулирует набор точек:

 template <typename T=std::vector<Point*>>
class Context {
   T container;
public:
   Point* addPoint(int x, int y) {
     Point *p = new Point(x, y);
     container.push_back(p);
     return p;
   }
};
  

Также вы можете захотеть использовать shared_ptr или unique_ptr избежать утечек памяти, хотя это может немного усложнить наследование.

Вот полностью WME на Ideone со 2-м вариантом:

 #include <iostream>
#include <vector>
using namespace std;

class Point {
    public:
    Point (int x, int y) {}
};

template <typename T=std::vector<Point*>>
class Context {
   T container;
public:
   Point* addPoint(int x, int y) {
     Point *p = new Point(x, y);
     container.push_back(p);
     return p;
   }
};

int main() {
    Context<> c;
    c.addPoint(1, 2);
    return 0;
}
  

Комментарии:

1. Я получаю «ошибка C2228: слева от ‘.push_back’ должен иметь class / struct / union» для строки container.push_back(p) . (Я использую вторую реализацию, которая включает в Context себя класс).

2. @Rokas oups, пожалуйста, посмотрите Мое обновление и MWE на Ideone; TLDR использовать std::vector<Point*> вместо std::vector

Ответ №2:

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

Это ваше решение, но подумайте дважды — в большинстве ситуаций лучше сохранять объекты как можно более простыми. Если вам нужно просто упростить свой код, это можно сделать:

Вы можете создать внешнюю функцию построения, аналогичную std::make_share и std::make_tuple:

Это позволит вам вызывать:

 construct<Point>(container, 1, 2);
construct<Line>(container, 1, 2, 3, 4);
  

И он построит Point / Line и поместит в контейнер в одной строке

Полный код:

 #include <iostream>
#include <vector>

using namespace std;

struct AbstractShape
{
    virtual std::ostreamamp; dump(std::ostreamamp;) = 0;
};
struct Point : AbstractShape
{
    Point(float x, float y) : x(x), y(y) {}


    virtual std::ostreamamp; dump(std::ostreamamp; o) override
    {
        return o << "P[" << x << ":" << y << "]";
    }
    float x, y;
};
struct Line : AbstractShape
{
    Line(float x1, float y1, float x2, float y2) : x1(x1), y1(y1), x2(x2), y2(y2) {}

    virtual std::ostreamamp; dump(std::ostreamamp; o) override
    {
        return o << "L[" << x1 << ":" << y1 << "," << x2 << ":" << y2<< "]";
    }

    float x1, y1, x2, y2;
};

template<typename Object, typename Container, typename ...Args>
Object* construct(Containeramp; c, Args... args)
{
    Object* res = new Object(args...);
    c.push_back(res);
    return res;
}
int main() {
    std::vector<AbstractShape*> container;

    construct<Point>(container, 1, 2);
    construct<Line>(container, 1, 2, 3, 4);

    for (auto s : container)
        s->dump(std::cout) << std::endl;

    return 0;
}
  

Вывод:

 P[1:2]
L[1:2,3:4]
  

Живой тест

И я определенно рекомендую использовать std::unique_ptr вместо необработанных указателей