Ссылка на std::array разных размеров вместо std::array в базовом классе

#c #arrays #vector #reference

#c #массивы #вектор #ссылка

Вопрос:

У меня следующая проблема. Рассмотрим классы

 class face {
    virtual std::vector<ptr>amp; get_vertices(void) const = 0;
};

class triangle : public face {
private:
    std::vector<ptr> vertices;
public:
    std::vector<ptr>amp; get_vertices(void) const { return vertices; };
};

class quadrilateral : public face {
private:
    std::vector<ptr> vertices;
public:
    std::vector<ptr>amp; get_vertices(void) const { return vertices; };
};
  

Очевидно, что треугольник и четырехугольник всегда будут иметь 3 и 4 вершины соответственно.
Таким образом, я хотел бы заменить std::vector на std::array соответствующего размера, чтобы сэкономить накладные расходы, вызванные тремя дополнительными указателями в std::vector. (Это потому, что у меня будут миллионы граней …) Теперь, есть ли шанс иметь общую функцию доступа совместно с std:: array, как с std:: vector выше?
Со стандартным C-массивом я бы просто вернул указатель на первую запись массива и ее размер. Есть ли STL-способ сделать то же самое или что-то подобное? Или есть какой-либо другой хороший способ достичь этой функциональности?

Спасибо за чтение и, возможно, за ответ!! Андреас

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

1. О чем additional pointers in std::vector вы говорите?

2. Вам действительно нужно возвращать их в виде контейнера STL? Если есть только 3 или 4 элемента, я не вижу большого преимущества в векторе или массиве. Я бы просто сохранил указатели в массиве фиксированного размера и использовал некоторую функцию или оператор, который возвращает элементы напрямую, например virtual ptramp; operator[](unsigned idx); , и другую функцию для получения их количества.

3. @Nawaz: Реализация std::vector (по крайней мере, для gcc) использует три указателя для начала хранения, завершения хранения и окончания хранения в дополнение к самим сохраненным данным. Это становится не пренебрегаемыми накладными расходами, если у вас есть огромное количество std :: vectors с относительно небольшим количеством записей.

4. @Timo: Это решение, о котором я думал, но оно не кажется «приятным» решением, подобным STL.

Ответ №1:

std::arrays разные размеры — это разные типы, поэтому я не вижу никакого способа сделать то, что вы хотите. Но что, если вы вернете итераторы begin() и end() const в любой контейнер, который вы храните внутри, или в объект малого диапазона, содержащий оба? Таким образом, вы отделяете размер контейнера от интерфейса, оставляя его на усмотрение реализации.

РЕДАКТИРОВАТЬ: Просто для пояснения, чтобы скрыть представление хранилища данных (в данном случае размер std::array ), вам понадобится ваш собственный класс итератора для face . Это легко реализовать с точки зрения указателей, поскольку для каждой face специализации вам известны размер, начало и конец базовой структуры данных. Но вы, очевидно, не можете напрямую использовать std::array ‘s begin() и end() в интерфейсе.

Пример:

Это быстрый и грязный пример, иллюстрирующий, как реализовать часть поведения прямого итератора с использованием указателей. Я использую виртуальный базовый класс и одну из реализаций из OP в качестве отправной точки. Я также опустил все конструкторы, операторы присваивания и т.д. Он также предполагает наличие ребра класса (предположительно, 2D или 3D точки?).

 class face {

public:

  typedef Edge* iterator;
  typedef const Edge* const_iterator;

  virtual iterator begin() = 0;
  virtual const_iterator begin() const = 0;
  virtual iterator end() = 0;
  virtual const_iterator end() const = 0;
  virtual size_t size() const = 0;

  virtual ~face() {};

};

class triangle : public virtual face {

public :
  virtual iterator begin() {return m_edges.begin();}
  virtual const_iterator begin() const {return m_edges.begin();}
  virtual iterator end() {return m_edges.end();}
  virtual const_iterator end() const {return m_edges.end();}
  virtual size_t size() const {return m_edges.size();}

private:
    std::array<Edge, 3> m_edges;
  

};

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

1. Это кажется подходящим решением. Однако я подумал, что может быть «более приятное» решение, т. Е. уже реализован некоторый итератор, не зависящий от размера массива. В любом случае, большое спасибо!!

2. @Andreas, я добавил простой пример, просто чтобы дать представление о простой реализации. Пожалуйста, отнеситесь к этому с осторожностью, это было сделано в спешке!

Ответ №2:

std::array здесь, похоже, нет решения, поскольку get_vertices это чисто виртуальная функция, это означает, что вы хотели бы получить к ней доступ, используя указатель (или ссылку) типа базового класса. И если вы используете std::array , вы должны предоставить целочисленное значение в качестве второго аргумента для std::array шаблона класса, что возможно, если вы создадите face шаблон класса, что-то вроде этого:

 template<size_t N>
class face {
    virtual std::array<ptr, N>amp; get_vertices(void) const = 0;
};
class triangle : public face<3>{
    //...
    std::array<ptr, 3>amp; get_vertices(void) const { return vertices; };
};
class quadrilateral : public face<4> {
    //...
    std::array<ptr, 4>amp; get_vertices(void) const { return vertices; };
};
  

Но это приводит к тому, что triangle и quadrilateral будут иметь разные базовые классы: face<3> и face<4> — это два разных класса. Это означает, что вы не можете смешивать triangle и quadrilateral вместе, скажем, в стандартном контейнере, и вы не можете получить доступ get_vertices с использованием указателей одного и того же типа базового класса, поскольку единого базового класса сейчас не существует. Каждый производный класс имеет свой собственный базовый класс.

Итак, решение таково:

 class triangle : public face {
private:
    std::vector<ptr> vertices;
public:
    triangle() 
    {
           //it ensures that you've a vector of size 3, no more no less 
           vertices.reserve(3); 
    }
    std::vector<ptr>amp; get_vertices(void) const { return vertices; };
};
  

Аналогично, вы можете сделать в quadrilateral :

     quadrilateral() 
    {
           //it ensures that you've a vector of size 4, no more no less 
           vertices.reserve(4); 
    }
  

Теперь ваш вектор не будет произвольно изменять свой размер до большего размера, чем ему действительно нужно, потому что вы определили емкость с помощью вызова reserve() , и вы добавите больше элементов, чем его емкость. Изменение размера не происходит.

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

1. Как и в моем комментарии выше, это спасает меня только от «дорогостоящих» перераспределений, но не от вызванных накладных расходов std::vector.