Russian Qt Forum

Программирование => С/C++ => Тема начата: __Heaven__ от Июнь 14, 2016, 12:19



Название: Шаблон треугольника
Отправлено: __Heaven__ от Июнь 14, 2016, 12:19
Привет, друзья!
Не часто приходилось писать свои шаблоны. Прошу помочь.
Есть класс треугольника, реализованный через QVector3D
Код
C++ (Qt)
class Triangle{
public:
   Triangle();
   Triangle(const QVector3D &a, const QVector3D &b, const QVector3D &c);
 
   QVector3D a() const;
   QVector3D b() const;
   QVector3D c() const;
 
   QVector3D ab() const;
   QVector3D bc() const;
   QVector3D ac() const;
 
   QVector3D normal() const;
   QVector3D normal(const QVector3D &pointUnderPlane) const;
   QVector3D perpendicular() const;
 
   float square() const;
   QVector3D center() const;
 
private:
   std::array<QVector3D, nodeCountPerFace> vertices;
};
 
using TriangleList = QVector<Triangle>;
 
Мне понадобилось увеличить точность до двойной и я решил в качестве вершин использовать класс сторонней библиотеки arma::vec3.
Как бы мне сделать шаблон так, чтобы можно было работать и с QVector3D и с arma::vec3?
Сейчас основная проблема: точность площади. Я могу либо оставить её всегда double, либо передавать 2 аргумента шаблона. Как бы поступили вы?


Название: Re: Шаблон треугольника
Отправлено: Igors от Июнь 14, 2016, 15:38
Хранить вертексы в полигоне - неудачная затея. Всегда есть операции применяемые ко всем вертексам и "доставать" их из полигонов будет очень неудобно (особенно учитывая что один вертекс может использоваться неск полигонами). Храните в полигонах индексы, а вертексы в отдельном контейнере, вот он может быть (и обычно бывает) разных типов. Напр так
Код
C++ (Qt)
struct VertexList {
virtual size_t size( void ) const = 0;
 
virtual arma::vec3 GetVertex( size_t index ) = 0;
virtual void SetVertex( const arma::vec3 & val,  size_t index ) = 0;
};
 
template <class T>
struct TVertexList<T> : public VertexList {
virtual size_t size( void ) const  { return mData.size(); }
 
virtual arma::vec3 GetVertex( size_t index ) { return mData[index]; }
virtual void SetVertex( const arma::vec3 & val,  size_t index ) { mData[index] = val; }
 
std::vector<T> mData;
};
Так одни и те же полигоны работают с разными представлениями вертексов, надо только добавить операторы перевода vec3 <-> QVector3D
Сейчас основная проблема: точность площади. Я могу либо оставить её всегда double, либо передавать 2 аргумента шаблона. Как бы поступили вы?
Не понял что за проблема. Если площадь оказывается меньше допустимого предела - полигон некорректен и должен быть удален. При этом возможно/часто модель должна быть перестроена. Использование double само по себе не решение. Напр если 3 точки лежат на 1 прямой - площадь нулевая при любой точности.


Название: Re: Шаблон треугольника
Отправлено: __Heaven__ от Июнь 14, 2016, 21:44
Нет, индексы я пока что не буду использовать. А если буду, то заведу под это отдельный класс. Про площадь я имел в виду, возвращаемый тип float/double как разрешить при помощи шаблона.

Что-то мне кажется, что надо отказываться от QVector3D и переходить на arma::Row<T>::fixed<3>...


Название: Re: Шаблон треугольника
Отправлено: Igors от Июнь 15, 2016, 10:34
Про площадь я имел в виду, возвращаемый тип float/double как разрешить при помощи шаблона.
А зачем его "разрешать"? Просто возвращайте "старший" тип double, не вижу чем это грозит. Да, и если плохие тр-ки образуются в результате boolean операции (напр рассечение полигона), то двойная точность не спасает.


Название: Re: Шаблон треугольника
Отправлено: __Heaven__ от Июнь 15, 2016, 10:35
Ну, как бы не хотелось бы тратиться на преобразования флоат-дабл и обратно. Хоть они и незначительны.


Название: Re: Шаблон треугольника
Отправлено: Igors от Июнь 15, 2016, 11:03
Ну, как бы не хотелось бы тратиться на преобразования флоат-дабл и обратно. Хоть они и незначительны.
Ф-ция/метод возвращает рез-т в регистре FPU (насколько помню 10 байт) и только при записи рез-та в память происходит конвертация в 4, 8 или 10 байт - но в любом случае одной командой.


Название: Re: Шаблон треугольника
Отправлено: __Heaven__ от Июнь 15, 2016, 13:21
тогда зачем придумали тригонометрические функции с суффиксом f? (sinf, cosf)


Название: Re: Шаблон треугольника
Отправлено: Igors от Июнь 16, 2016, 10:58
тогда зачем придумали тригонометрические функции с суффиксом f? (sinf, cosf)
Чтобы загрублять точность аргумента и рез-та.

Возможно Вы имели ввиду что реализации square нужны разные, напр из-за разницы в оператораx QVector3D и arma::vec3. Тогда так
Код
C++ (Qt)
template <class T>
class Triangle {
public:
   Triangle();
   Triangle(const T &a, const T &b, const T &c);
 
   T a() const;
//....
private:
   std::array<T, nodeCountPerFace> vertices;};
 
Т.е. переписываете с "T" вместо QVector3D. A потом объявляете специализацию для QVector3D и/или arma::vec3
Код
C++ (Qt)
template<> class Triangle<QVector3D> {
public:
float square( void );
};
 
Все же практичнее использовать классы с одинаковыми базовыми операторами/методами. отличающиеся только float/double


Название: Re: Шаблон треугольника
Отправлено: m_ax от Июнь 16, 2016, 13:27
Цитировать
Т.е. переписываете с "T" вместо QVector3D. A потом объявляете специализацию для QVector3D и/или arma::vec3
Специализировать целый класс ради одной-двух функций?) 


Название: Re: Шаблон треугольника
Отправлено: Racheengel от Июнь 16, 2016, 14:46
А зачем тут шаблон? Не проще сделать общий интерфейс и 2 имплементации к нему?


Название: Re: Шаблон треугольника
Отправлено: m_ax от Июнь 16, 2016, 15:04
А зачем тут шаблон? Не проще сделать общий интерфейс и 2 имплементации к нему?
Нет конечно) И потом, почему именно 2? И как Вы себе представляете общий интерфейс для этого класса?

Шаблоны здесь кстатии, только я не согласен вот с этим подходом:
Цитировать
Т.е. переписываете с "T" вместо QVector3D. A потом объявляете специализацию для QVector3D и/или arma::vec3


Название: Re: Шаблон треугольника
Отправлено: Igors от Июнь 16, 2016, 15:22
Специализировать целый класс ради одной-двух функций?) 
Да, верно (недавно обсуждали подобное). Хорошо, а если так?
Код
C++ (Qt)
template<class T>
class TriangleQ : public Triangle<T> {
public:
       TriangleQ(const T &a, const T &b, const T &c) : Triangle<T>(a, b, c)
{
}
 
float square( void ) const
{
return 1;
}
};
 


Название: Re: Шаблон треугольника
Отправлено: m_ax от Июнь 16, 2016, 15:58
Цитировать
Хорошо, а если так?
И чем это лучше? Всё равно придётся копипастить весь класс..

Я имел в виду завести point_traits, где определить нужные функции и подсовывать уже его в реализацию одного шаблонного треугольника:
Код
C++ (Qt)
template <class>
point_traits
{};
 
template <>
point_traits<QVector3D>
{
   typedef float value_type;
 
   static value_type square(const QVector3D & p1, const QVector3D & p2, const QVector3D & p3) { return ...; }
};
 
template <>
point_traits<arma::vec3>
{
   typedef double value_type;
 
   static value_type square(const arma::vec3 & p1, const value_type & p2, const value_type & p3) { return ...; }
};
 
template <class T, class PointTraits = point_traits<T>>
class Triangle
{
public:
   typedef T point_type;
   typedef typename PointTraits::value_type value_type;
...
  value_type square() const { return PointTraits::square(p1, p2, p3); }
 
private:
  point_type p1;
  point_type p2;
  point_type p3;
};
 


Название: Re: Шаблон треугольника
Отправлено: Racheengel от Июнь 16, 2016, 16:22
А зачем тут шаблон? Не проще сделать общий интерфейс и 2 имплементации к нему?
Нет конечно) И потом, почему именно 2? И как Вы себе представляете общий интерфейс для этого класса?

Потому что сейчас речь идет о QVector3d, который основан на float, и другой класс, который использует double для внутреннего представления. Скорее всего, и API у них также различны. А значит, шаблонное решение тут очень спорно - нет "общих точек соприкосновения". Поэтому по хорошему автору нужен враппер, API которого будет независимо от внутреннего API векторных классов. В интерфейс надо вынести функции, которые имеют значение для обоих классов, и принимают в качестве параметром только примитивные типы. А вот уже эти типы можно описать с помощью шаблонов (типа Вашего последнего примера). Если же автор захочет иметь метод, который раньше возвращал QVector3d - то шаблоны не спасут.


Название: Re: Шаблон треугольника
Отправлено: m_ax от Июнь 16, 2016, 16:46
Цитировать
Потому что сейчас речь идет о QVector3d, который основан на float, и другой класс, который использует double для внутреннего представления.
А завтра ТС захочет 3-ий, 4-ый и 5-ый)

Цитировать
Поэтому по хорошему автору нужен враппер, API которого будет независимо от внутреннего API векторных классов.
Сейчас оно уже не зависимо)

Цитировать
В интерфейс надо вынести функции, которые имеют значение для обоих классов, и принимают в качестве параметром только примитивные типы.

А если завтра их захочется больше (в смысле классов)?

Цитировать
Если же автор захочет иметь метод, который раньше возвращал QVector3d - то шаблоны не спасут.
Не понял? Почему?

Можно кодом продемонстрировать, как Вы это представляете?


Название: Re: Шаблон треугольника
Отправлено: Racheengel от Июнь 16, 2016, 16:57
Я имею в виду следующее: у автора есть методы типа

class Triangle{
public:
...
    QVector3D a() const;
...
};


Т.е класс Triangle "заточен" на работу с QVector3D. В общем случае это не годится, и вместо QVector3D придется использовать независимый от имплементации класс-враппер, типа CUniversalVector3D, который уже может быть специализирован для double & float отдельно. Это, в свою очередь, означает смену API:

class Triangle{
public:
...
    CUniversalVector3D a() const;
...
};

только тогда человек сможет "безболезненно" менять имплементации векторов.


Название: Re: Шаблон треугольника
Отправлено: m_ax от Июнь 16, 2016, 17:09
Цитировать
только тогда человек сможет "безболезненно" менять имплементации векторов.
Безболезненно? Вы шутите?) То что Вы сейчас предлагаете - это антипатерн..
Это как минимум не эффективно: приводить суперкласс к частным случаям (QVector3D, arma::vec3).. И как быть с тем, что завтра мне понадобится использовать в качестве вектора /точки другой класс? (например с размерностью больше 3?) Ммм? Будете опять лезть в супервектор и добавлять новый функционал? И так до бесконечности?

Нет уж, нафиг-нафиг)         


Название: Re: Шаблон треугольника
Отправлено: __Heaven__ от Июнь 16, 2016, 17:15
Я имел в виду завести point_traits, где определить нужные функции и подсовывать уже его в реализацию одного шаблонного треугольника:
Элегантненько, спасибо :)


Название: Re: Шаблон треугольника
Отправлено: __Heaven__ от Июнь 16, 2016, 17:22
Racheengel, если я правильно вас понял, то сделать больше двух (и даже одного, имхо) наследников будет болью. Так и зачем нам привязка именно к QVector3D не совсем понял.


Название: Re: Шаблон треугольника
Отправлено: __Heaven__ от Июнь 16, 2016, 17:53
m_ax, а зачем в аргументы шаблона записывать class PointTraits = point_traits<T> ?


Название: Re: Шаблон треугольника
Отправлено: __Heaven__ от Июнь 16, 2016, 17:55
А всё, сообразил, чтобы какой-нибудь Generic можно было вставить


Название: Re: Шаблон треугольника
Отправлено: m_ax от Июнь 16, 2016, 17:59
m_ax, а зачем в аргументы шаблона записывать class PointTraits = point_traits<T> ?
Если понадобится расширить класс харрактеристик (в котором еобходимо передавать более одного шаблона).. Но можно и не писать, если в этом нет необходимости..


Название: Re: Шаблон треугольника
Отправлено: Racheengel от Июнь 16, 2016, 22:51
Racheengel, если я правильно вас понял, то сделать больше двух (и даже одного, имхо) наследников будет болью. Так и зачем нам привязка именно к QVector3D не совсем понял.

Боль будет в любом случае, поскольку изначальный класс завязан на QVector3D , с которым double не получится...
А значит, и весь код, зависящий от этого класса, придется переделать на нечто генерическое с абстрактым вектором.
Но после этого хоть 2, хоть 22 наследника можно делать - все будет более-менее одинаково. Главное API не поломать.


Название: Re: Шаблон треугольника
Отправлено: Racheengel от Июнь 16, 2016, 22:55
Безболезненно? Вы шутите?) То что Вы сейчас предлагаете - это антипатерн..

С каких пор "фасад" стал антипаттерном? O_o

Это как минимум не эффективно: приводить суперкласс к частным случаям (QVector3D, arma::vec3).. И как быть с тем, что завтра мне понадобится использовать в качестве вектора /точки другой класс? (например с размерностью больше 3?) Ммм? Будете опять лезть в супервектор и добавлять новый функционал? И так до бесконечности?

Суперкласс сейчас завязан на QVector3D. Если его привести к CAbstractVector3D, то имплементаций можно ваять сколько угодно - но код, использующий CAbstractVector3D, переписывать не придется :)


Название: Re: Шаблон треугольника
Отправлено: m_ax от Июнь 16, 2016, 23:51
Цитировать
С каких пор "фасад" стал антипаттерном? O_o
А я сомневаюсь, что это в данном случае именно фасад)

Ну хорошо, как Вы видите себе API абстрактного CAbstractVector3D? Как Вы его будете приводить к QVector3D, arma::vec3, MyCustomPont5D?


Название: Re: Шаблон треугольника
Отправлено: Racheengel от Июнь 17, 2016, 01:17
// Это базовый шаблон-интерфейс, в который передаем T=float/double/whatever...
template<typename T>
class IAbstractVector3D
{
public:
   IAbstractVector3D(T x, T y, T z);

   IAbstractVector3D GetMul(T factor) const  = 0;
   IAbstractVector3D GetSomethingElse(...) const  = 0;
   
   T GetX() const = 0;
   T GetY() const = 0;   
   T GetZ() const = 0;
};

// float-имплементация на основе QVector3D
class CVector3D: public IAbstractVector3D<float>
{
public:
   CVector3D(float x, float y, float z): m_vector3d(x,y,z)
   {}

   IAbstractVector3D GetMul(T factor) const
   {
      QVector3D result = m_vector3d * factor;
      return CVector3D(result.x(), result.y(), result.z());
   }

...

  float GetX() const { return m_vector.x(); }
...

private:
   QVector3D m_vector3d;   
};


// double-имплементация на основе vec3
class CArmaVector3D: public IAbstractVector3D<double>
{
public:
   CArmaVector3D(double x, double y, double z): m_vector3d(x,y,z)
   {}

   IAbstractVector3D GetMul(double factor) const
   {
      CArmaVector3D result = m_vector3d * factor;
      return CArmaVector3D(result.x(), result.y(), result.z());
   }

...

  double GetX() const { return m_vector.x(); }
...

private:
   arma::vec3 m_vector3d;   
};

Примерно так у нас реализованы подобные классы в рабочей либе (сейчас писал по памяти, без компилятора, но в общем идея, думаю, понятна)


Название: Re: Шаблон треугольника
Отправлено: __Heaven__ от Июнь 17, 2016, 09:32
Зачем мне реализовывать GetMul?, если везде определён operator*, а если в какой-то экзотической либе не определён, то я могу его определить сам


Название: Re: Шаблон треугольника
Отправлено: Racheengel от Июнь 17, 2016, 10:03
Зачем мне реализовывать GetMul?, если везде определён operator*, а если в какой-то экзотической либе не определён, то я могу его определить сам

Ну, это я для примера привел :)
В реальности все скорее всего решится через Find & Replace :)
Даже имплементация через общий интерфейс имеет недостаток того, что в вызывающем коде все равно придется менять флоты на даблы. Поэтому, любое другое решение, кроме замены, будет избыточным :)



Название: Re: Шаблон треугольника
Отправлено: m_ax от Июнь 17, 2016, 10:36
Цитировать
Примерно так у нас реализованы подобные классы в рабочей либе (сейчас писал по памяти, без компилятора, но в общем идея, думаю, понятна)
Теперь понятно, спасибо)

Думаю, класс точки/вектора - это  немного не тот случай, когда паттерн фассад  оправдан.. Во всяком случае я не вижу никаких преимуществ в данном подходе..
Как я понимаю, пользователь сам должен выбирать удобную для него реализацию класса точки/вектора (с любой из которых сможет однообразно работать класс Triangle) а не переписывать каждый раз весь её функционал, наследуясь от абстрактного предка.
   


Название: Re: Шаблон треугольника
Отправлено: Racheengel от Июнь 17, 2016, 10:51
Во всяком случае я не вижу никаких преимуществ в данном подходе..

Да собственно, их и нет. Единственное, для чего все это городится - чтобы "встык" заменить QVector3D на нечто другое. Но проблема тут в том, что и вызывающий код придется насиловать в любом случае. Либо заменять все вызовы Triangle на IAbstractTriangle, либо переписать на шаблонах... Опять же, возникает вопрос - усилия того стоят?


Название: Re: Шаблон треугольника
Отправлено: __Heaven__ от Июнь 17, 2016, 13:09
Прочитал на википедии про паттерн фасад. Глядя на пример BlackSabbath ничего не понятно, в чём тут паттерн... Либо я интуитивно его постоянно использую. Есть что-то маленькое, которое входит в состав чего-то большого и имеет свою ответственность.


Название: Re: Шаблон треугольника
Отправлено: __Heaven__ от Июнь 17, 2016, 13:12
Код из первого моего поста ведь тоже фасад? Три точки, которые управляются бОльшим классом.


Название: Re: Шаблон треугольника
Отправлено: Igors от Июнь 17, 2016, 13:50
И чем это лучше? Всё равно придётся копипастить весь класс..
Нет, только конструкторы с аргументами, остальное унаследуется. Хотя стоит ли оно того - хз. Напр вот реализация
Код
C++ (Qt)
float Triangle::Area( void )
{
return QVector3D::crossProduct(vertices[1] - vertices[0], vertices[2] - vertices[1]).length() / 2;
}
 
Очевидно для arma::vec3 это не прокатит. Арифметика (-, /) там наверное есть, а вот статический crossProduct вряд ли, да и length может быть другим. То же самое случится со многими (если не всеми) содержательными методами, итог обобщения - неск косметических сеттеров.

Также template проблематичен и с др стороны. Допустим мы добились желаемого и теперь может свободно объявлять
Код
C++ (Qt)
QVector<Triangle<QVector3D> > vec1;
QVector<Triangle<arma::vec3> > vec2;
...
 
Ну и как теперь с этим работать? Напр был класс
Код
C++ (Qt)
struct Model {
..
QVector<Triangle> mPoly;
..
};
 
А теперь, когда Triangle стал шаблоном - какой контейнер писать? И к кому обращаться методам использующим mPoly? Утрированный пример:
Код
C++ (Qt)
QImage<ARGB_32>;
QImage<RGB_32>;
QImage<Monochrome>;
...
 
Минусы (или просто глупость) такого решения очевидны.

Мои предложения

1) Реализовать всю содержательную часть Triangle для "старшего" типа, здесь это arma::vec3 и только хранилище (vertices) имеет тип аргумента template
Код
C++ (Qt)
class Triangle {
 Triangle( arma::vec3 & a, arma::vec3 & b, arma::vec3 & c )
 {
   SetVer(0, a);
   SetVer(1, b);
   SetVer(2, c);
 }
// Содержательные методы
//
 virtual arama::vec3 GetVer( size_t index ) = 0;
 virtual void SetVer( size_t index, const arama::vec3 & val ) = 0;
};
 
template <class T>
class TriangleImpl : public Triangle {
 virtual arama::vec3 GetVer( size_t index ) { return vertices[index]; };
 virtual void SetVer( size_t index, const arama::vec3 & val ) { vertices[index] = val; }
 
 T vertices[3];
};
Теперь T может быть чем угодно - лишь бы он умел приводиться к arma::vec3, это легко обеспечить. Да, есть расходы на перегонку, но они терпимы.

2) Подобным образом "замкнуть" template внутри контейнера полигонов
Код
C++ (Qt)
struct CPolyList {
virtual Triangle & Get( size_t index );
};
 
template <class T>
struct CPolyListImp : public CPolyList {
virtual Triangle & Get( size_t index ) { return mData[index]; }
 
QVector<TroangleImpl<T> > mData;
};
 
Теперь мы можем писать
Код
C++ (Qt)
CPolyList * poly;
poly = new CPolyListImp<QVector3D>;
..
poly = new CPolyListImp<arma::vec3>;

3) Вот что-то мне никак не верится что есть необходимость в хранении фундаментальных данных (полигонов) 2-мя или более способами. Впечатление что площадь считается по формуле Герона - ну тогда да, точность низкая.


Название: Re: Шаблон треугольника
Отправлено: __Heaven__ от Июнь 17, 2016, 14:30
3) плавно перевести проект с использования одного типа на другой.


Название: Re: Шаблон треугольника
Отправлено: Igors от Июнь 17, 2016, 15:12
3) плавно перевести проект с использования одного типа на другой.
К слову: VBO с double не дружит. Ошибок не выдает, но тормоза вместо ускорения. Не смертельно, но придется на карту подать временный массив float. По поводу "плавности" - все-таки лобовая замена дешевле, обобщение здесь оказывается совсем не простым.

Безболезненно? Вы шутите?) То что Вы сейчас предлагаете - это антипатерн..
Это как минимум не эффективно: приводить суперкласс к частным случаям (QVector3D, arma::vec3)..
Да, но эти скромные потери все же лучше чем все инфицировано template, там полный тупик.

И как быть с тем, что завтра мне понадобится использовать в качестве вектора /точки другой класс? (например с размерностью больше 3?) Ммм? Будете опять лезть в супервектор и добавлять новый функционал? И так до бесконечности?
Нет уж, нафиг-нафиг)         
Кстати размерность 4 - вполне реальный случай, так называемые "однородные" координаты, 4-е число - делитель. Часто используется для хранения перспективы, напр в OpenGL. Да, возможно тогда придется чего-то "специализировать", напр вычисление центра тр-ка, при этом приводясь к явному классу. Но это неизбежно, мы не можем получить 4-ю если базовый вектор имеет 3.


Название: Re: Шаблон треугольника
Отправлено: Old от Июнь 17, 2016, 16:05
Минусы (или просто глупость) такого решения очевидны.
Это точно.

Но, как я понял, __Heaven__ хотел простого перехода от одного треугольника к другому, а не совместно их использовать в проекте:
Код
C++ (Qt)
#ifdef USE_QVECTOR3D
using Triangle = TriangleBase<QVector3D>;
#else
using Triangle = TriangleBase<arma::vec3>;
#endif
 
vector<Triangle> vec;
list<Triangle> lst;
 


Название: Re: Шаблон треугольника
Отправлено: __Heaven__ от Июнь 17, 2016, 16:38
Old, в точку.


Название: Re: Шаблон треугольника
Отправлено: __Heaven__ от Июнь 17, 2016, 16:40
Igors, да. Vbo будет наполняться float.


Название: Re: Шаблон треугольника
Отправлено: __Heaven__ от Январь 30, 2017, 11:00
Кстати, не рассматривался вариант шаблонной обёртки для точки с оператором преобразования к типу. Он будет как-то хуже, чем предложенный с point_traits?