LINUX.ORG.RU

[c++] можно ли (1) недопустить «лишнее» наследование или (2) узнать реальный класс в конструкторе или (3) сделать надежный pattern matching

 


0

0

Вот пример (наивной) попытки:

template<class T1, class T2> class MatchableFor
{
public:
  MatchableFor(): what(0) 
  { 
    if(настоящий класс это не T1 и не T2, узнается с помощью (2) )
      throw SomeError;
    else if( настоящий класс это T1 )
      what=1;
    else if( настоящий класс это T2 )
      what=2;
  }
  virtual void run(Runnable< MatchableFor<T1,T2> > r1, Runnable< MatchableFor<T1,T2> > r2) 
  {
    what==0 ? throw SomeError : 
    what==1 ? r1.run(dynamic_cast<T1*>this) :
    what==2 ? r2.run(dynamic_cast<T2*>this) :
              throw SomeError; 
  }
private:
  int what;
};
class Child1;
class Child2;

struct Parent: public MatchableFor<Child1, Child2>
{
  virtual void do_something()=0;
};

class Child1: public Parent { void do_something() { f(); } }; // ok
class Child2: public Parent { void do_something() { g(); } }; // ok
class Child3: public Parent { void do_something() { h(); } }; // а вот это можно недопустить с помощью (1)

вроде бы (2) вообще невозможно, а (1) и (3) мне неизвестно

★★★★★

Последнее исправление: www_linux_org_ru (всего исправлений: 3)

Ответ на: комментарий от Booster

да, другое, нужен *реальный* класс из которого этот конструктор вызвался, т.е. например Child1 если в конечном итоге инстанциировался Child1.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от www_linux_org_ru

Это как? Получить тип производного в базовом? Передать в качестве параметра базовому. И по-моему параметр шаблона идентичный самому типу, нельзя передать, получим бесконечную рекурсию.

Booster ★★
()
Ответ на: комментарий от Booster

Это как? Получить тип производного в базовом? Передать в качестве параметра базовому.

так?

// $ ./a.out 
// 12MatchableFor
// 6Child1

#include <iostream>
#include <typeinfo>

class MatchableFor
{
public:
  MatchableFor(MatchableFor* c) { std::cout << typeid(*c).name() << "\n" ;}
  virtual void do_something()=0;
private:
  MatchableFor() {}
};

struct Child1: public MatchableFor
{
  Child1(): MatchableFor(this) { std::cout << typeid(*this).name() << "\n" ; }
  virtual void do_something() { std::cout << "asdf\n"; }
};

int main(int argc, char** argv)
{
  Child1 c;
  return 0;
}

не получается

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от www_linux_org_ru

Как-то так:

#include <iostream>
#include <typeinfo>

template <typename Child>
class Base
{
public:
	Base()
	{
		std::cout << "Child is: " << typeid(Child).name() << std::endl;
	}
};

class Test1 : public Base<Test1>
{
};

int main(int argc, char ** argv) 
{ 
	Test1 t1;
} 

Booster ★★
()
Ответ на: комментарий от Booster

Так я могу, но толку от этого ровно 0.

Мне надо чтобы

или class Child3: public Parent { void do_something() { h(); } }; компилятор отвергнул (понятно, это самое лучшее)

или хотя бы при попытке Parent* x = new Child3(); выкинулся бы эксепшн (это можно было бы сделать через (2), но твой способ не помогает).

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от www_linux_org_ru

Честно говоря не понимаю, что нужно. Запретить наследование или разрешить наследование определённым классам, а всем другим нет? Или чтобы могли наследоваться один раз?

Booster ★★
()
Ответ на: комментарий от Booster

Нужно разрешить наследование, но мочить каждого третьего наследника. И в идеале - на этапе компиляции. ;)))

LamerOk ★★★★★
()
Ответ на: комментарий от Booster

нужно, чтобы после struct Parent: public MatchableFor<Child1, Child2> { .... } от Parent-а могли отнаследоваться только Child1 и Child2, а Child3 например не мог.

(и кажется я понял, как это сделать, правда с макросом)

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от www_linux_org_ru

>нужно, чтобы после struct Parent: public MatchableFor<Child1, Child2> { .... } от Parent-а могли отнаследоваться только Child1 и Child2, а Child3 например не мог.
Это как? Насколько я понимаю, в параметр шаблону нельзя засунуть производный или тот-же самый класс. То есть параметр не может быть тем же самым классом.

Booster ★★
()
Ответ на: комментарий от www_linux_org_ru

А чем тебе множественное наследование не угодило? Ведь ты тужишься реализовать самый обычный интерфейс, только каким-то черезвычайно самобытным образом.

LamerOk ★★★★★
()

Выкинь каку.
Зачем тебе это? В продакшн C++ такое пускать все-равно нельзя. А поиграться - возьми CL, там МП и ООП намного круче.

Love5an
()
Ответ на: комментарий от Love5an

Зачем тебе это? В продакшн C++ такое пускать все-равно нельзя. А поиграться - возьми CL, там МП и ООП намного круче.

я уж объяснял зачем — добраться до границ с++ и ощутить, как они неудобны.

насчет МОР — если будет не лень, переформулирую задачу, но сильно сомневаюсь, что обычный лисп (не Qi) тебе что-нить статически гарантирует — короче, хрен ты там такое напишешь (а если не надо статических гарантий — я бы вообще этот вопрос не задавал)

ладно, вот че получилось:

#include <iostream>

/// library

template<class T> class Runnable { void run(T*) {} };

template<class T1, class T2> class MatchableFor
{
protected:
  enum RealClass { T_1=1, T_2=2 };
  RealClass rc;
public:
  typedef MatchableFor<T1,T2> ThisMatchable;
  MatchableFor(RealClass rc): rc(rc) {}
  static RealClass real_class(const T1& arg) { return T_1; }
  static RealClass real_class(const T2& arg) { return T_2; }
  void debug() { std::cout << int(rc) << "\n"; }
private:
  MatchableFor() {}
  MatchableFor(MatchableFor&) {}
};
#define BE_CHILD_OF(Parent) Parent(real_class(*this))

/// usage:

class Child1;
class Child2;

struct Parent: public MatchableFor<Child1, Child2>
{
  Parent(RealClass rc): ThisMatchable(rc) {} /// да boilerplate, но неясно, как иначе
  virtual void do_something()=0;
};

struct Child1: public Parent
{
  Child1(): BE_CHILD_OF(Parent) {}
  void do_something() { std::cout << "Child1\n"; }
};

struct Child2: public Parent
{
  Child2(): BE_CHILD_OF(Parent) {}
  void do_something() { std::cout << "Child2\n"; }
};

#ifdef BAD
struct Child3: public Parent
{
  Child3(): BE_CHILD_OF(Parent) {}
  void do_something() { std::cout << "Child3\n"; }
};
#endif

int main(int argc, char** argv)
{
  Child1 c1; c1.debug();
  Child2 c2; c2.debug();

  return 0;
}

тут уже осталось строк 100 до некого подобия полноценного паттерн матчинга

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от Booster

> Это как? Насколько я понимаю, в параметр шаблону нельзя засунуть производный или тот-же самый класс. То есть параметр не может быть тем же самым классом.

Можно, если осторожно :-)

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от LamerOk

> А чем тебе множественное наследование не угодило? Ведь ты тужишься реализовать самый обычный интерфейс, только каким-то черезвычайно самобытным образом.

Допустим, есть у нас готова иерархия классов из Parent, Child1 и Child2, и мы хотим прикрутить к ней паттерн матчинг с минимальными текстуальными изменениями. При этом, если в будущем к иерархии будет добавлен Child3, то паттерн матчинг не должен внезапно сломаться в рантайме, а просигналить во время компиляции.

Хотя паттерн матчингом это называть рано... тут пока типа визитора что-то нарисовано...

www_linux_org_ru ★★★★★
() автор топика



если кто знает, как тут обойтись без макроса (который на самом деле нихрена почти не абстрагирует) и без boilerplate code или что это невозможно, желательно кинуть ссылку.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от www_linux_org_ru

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

Твою «проблему» решает абстрактный метод, который обязан быть реализован в детях. Если в дитё3 его не реализовать - ничего не соберется. Вот тебе и весь твой «паттерн-матчинг». xDD

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

LamerOk ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

Под МП я имел ввиду не столько MOP, сколько макросы.
Пишем макрос-надстройку над defclass/defgeneric/defmethod и делаем там что угодно. В т.ч. и что-либо с тем же MOP.
А C++ убог и уродлив. На нем что-либо такое вытворять это все равно, что в говно тыкать палочкой.

Love5an
()

>нужно, чтобы после struct Parent: public MatchableFor<Child1, Child2> { .... } от Parent-а могли отнаследоваться только Child1 и Child2, а Child3 например не мог.
Ограничение по именам, зачем? Что это даёт?

Booster ★★
()
Ответ на: комментарий от LamerOk

бум считать что ты меня спросил «а нафига ты реализуешь свой virtual dipatch»

отвечу:

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

2. ну и в целях модульности, чтобы не лезть в другой класс

по п.1 сходу пример (правда м.б. там можно проще выкрутиться, попробуй)

template<unsigned int n> struct Vector { double value[n]; }

struct AbstractUnaryFunction
{
  template<unsigned int n> 
  virtual void modify(Vector<n>& v) = 0;
};

struct Square: public AbstractUnaryFunction
{
  template<unsigned int n>
  virtual void modify(Vector<n>& v) { for(int i=0; i<n; i++) v[i]*=v[i]; }
};

struct Cube: public AbstractUnaryFunction
{
  template<unsigned int n>
  virtual void modify(Vector<n>& v) { for(int i=0; i<n; i++) v[i]*=v[i]*v[i]; }
};

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от Love5an

> А C++ убог и уродлив. На нем что-либо такое вытворять это все равно, что в говно тыкать палочкой.

C тобой мы лучше протестим скорость обработки эксепшенов в лиспе. Зубок мне объяснил, как идет 29-битная арифметика, я смогу написать плюсовый эквивалент.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от www_linux_org_ru

тестировали уже(throw/catch из CL), C++ тормозит

Настоящие же исключения(те, которые полиморфные, имеют блок finally и т.п.) и, тем более, кондишены(не раскручивающие стек и т.п.), с C++ сравнивать глупо, по причине их отсутствия в последнем.

Love5an
()
Ответ на: комментарий от Love5an

>Настоящие

Нормальные


хотя, это одно и то же, в принципе

Love5an
()

О-па! ЛОР научился синтаксис подсвечивать? О_О

zfsed
()
Ответ на: комментарий от www_linux_org_ru

Последняя задача на плюсах не имеет решений. Плюсы в принципе не позволяют диспечеризацию типов по значениям в рантайм, кроме как через вэтэйбл. Альтернатива только enum.


1. «Много» - это сколько? Сотни? Тысячи? Сотни тысяч?
2. Я ж тебе объяснил - абстрактный класс идет отдельно. Там, где тебе нужна проверка - наследуешь и делаешь вызов. Ну нет в плюсах интерфейсов, нету.

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

>Плюсы в принципе не позволяют диспечеризацию типов по значениям в рантайм, кроме как через вэтэйбл.
Так дело в этом что-ли? Зачем? Для тормозов?

Booster ★★
()
Ответ на: комментарий от LamerOk

> Последняя задача на плюсах не имеет решений.

А если найду? :-)

«Много» - это сколько? Сотни? Тысячи? Сотни тысяч?

статически неизвестное количество

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от LamerOk

Не найдешь. Я уже объяснил, почему.

А я попробую.

#include <iostream>

/// library

template<class T0, class T1, class T2> class MatchableNest
{
protected:
  enum RealClass { T_1, T_2 };
  RealClass rc;
public:
  typedef MatchableNest<T0,T1,T2> TheMatchableNest;
  MatchableNest(RealClass rc): rc(rc) {}
  static RealClass real_class(const T1& arg) { return T_1; }
  static RealClass real_class(const T2& arg) { return T_2; }
  void debug() { std::cout << int(rc) << "\n"; }
  template<class A> void run1(A& arg, void f1(const T1&, A&), void f2(const T2&, A&) )
  {
    switch(rc)
    {
      case T_1: f1( dynamic_cast<const T1&>(*this), arg ); return;
      case T_2: f2( dynamic_cast<const T2&>(*this), arg ); return;
    }
  }
  virtual ~MatchableNest() {}
private:
  MatchableNest() {}
  MatchableNest(MatchableNest&) {}
};

#define BE_CHILD_OF(Parent) Parent(real_class(*this))

/// usage:

typedef unsigned int uint;

template<uint n> struct Vector 
{
  double value[n];
  void print() { for(uint i=0; i<n; i++)  std::cout << i << "->" << value[i] << "\n"; }
};

struct AbstractUnaryFunction;
struct Square;
struct Cube;

struct AbstractUnaryFunction: public MatchableNest<AbstractUnaryFunction, Square, Cube>
{
  AbstractUnaryFunction(RealClass rc): TheMatchableNest(rc) {}
};

template<uint n>
void modify(const AbstractUnaryFunction&, Vector<n>& v) { throw 0; }

struct Square: public AbstractUnaryFunction
{
  Square(): BE_CHILD_OF(AbstractUnaryFunction) {}
};

template<uint n>
void modify(const Square&, Vector<n>& v) { for(uint i=0; i<n; i++) v.value[i]*=v.value[i]; }

struct Cube: public AbstractUnaryFunction
{
  Cube(): BE_CHILD_OF(AbstractUnaryFunction) {}
};

template<uint n>
void modify(const Cube&, Vector<n>& v) { for(uint i=0; i<n; i++) v.value[i]*=v.value[i]*v.value[i]; }

int main(int argc, char** argv)
{
  Vector<3> v; v.value[0]=0; v.value[1]=1; v.value[2]=2;
  AbstractUnaryFunction* f;
  if( argc>1 )
    f = new Cube();
  else
    f = new Square();
  v.print();
  f->run1(v, modify<3>, modify<3>); // для красоты я поленился писать еще один шаблон
  v.print();
  return 0;
}

www_linux_org_ru ★★★★★
() автор топика
> class Child1: public Parent { void do_something() { f(); } }; // ok 
> class Child2: public Parent { void do_something() { g(); } }; // ok 
> class Child3: public Parent { void do_something() { h(); } }; // а вот это можно недопустить с помощью (1) 

А как насчёт

class Child4 : public Child1{};
?

Miguel ★★★★★
()
Ответ на: комментарий от Miguel

> А как насчёт class Child4 : public Child1{};

Учитывая LSP можно считать, что Child4==Child1 с точки зрения проектировщика. Я тут не собираюсь сделать полноценные виртуальные функции со странным ограничением, а именно паттерн матчинг, который будет формализовывать рассуждения вида «каждый Parent это или Child1 (и возможно потомки, но нам пофиг) или Child2 (и возможно потомки, но нам пофиг), других вариантов нет».

Имея такую вещь, можно с помощью run1 (которую надо было бы назвать погромче — virtualize) виртуализовать твои Cons-ы из задачи про равные длины векторов. Собственно, оттуда эта идея и пошла (правда, там еще много писанины осталось).

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от www_linux_org_ru

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

Booster ★★
()
Ответ на: комментарий от www_linux_org_ru

>> Не найдешь. Я уже объяснил, почему.

А я попробую.


Я имел в виду последнюю из задач по тексту - типизацию на основе значений. Текующую задачу шаблонами и препроцессором решить скорее всего можно.

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

> Я имел в виду последнюю из задач по тексту - типизацию на основе значений. Текующую задачу шаблонами и препроцессором решить скорее всего можно.

Пояснее выражайся. Если ты про dependent types — их тоже можно наваять на плюсах, только дофига писанины и фантомных типов.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от Booster

> Объясните мне пожалуйста, что это за ограничение по именам производных классов? Зачем оно? Мне очень интересно.

(я щас торможу) Оно предполагалось для реализации MatchableNest. Сейчас там весьма костыльный rc. Смотреть надо на строчки с dynamic_cast.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от Miguel

> Тады почему просто не сделать в Parent приватный конструктор, а деток объявить френдами?

не въехал в твою идею.

во всяком случае запретить делать новых потомков от Child1 мы все равно так не сможем, вроде бы это можно сделать только в теле самого Сhild1.

www_linux_org_ru ★★★★★
() автор топика
Ответ на: комментарий от www_linux_org_ru

Я вот про это: template<uint n>

Как ты понимаешь, это _не_ решение. Это лютый ....

LamerOk ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

С френдами верно было замечено. Страуструп такое предлагал:

class Usable;
class Usable_lock
{
        friend class Usable;
private:
        Usable_lock() {}
};

class Usable : public virtual Usable_lock
{
public:
        Usable() {}
};

class DD : public Usable
{};

int main (int argc, char* argv[])
{
        Usable u; //ok
        DD d; //error
}

Booster ★★
()
Ответ на: комментарий от www_linux_org_ru

Ну, ты же спрашиваешь про «не допустить лишнее наследование»?

Если у класса Parent все конструкторы приватные, а два его потомка объявлены друзьями, то других потомков, конечно, создавать можно, только вот значений соответствующих типов не будет.

запретить делать новых потомков от Child1 мы все равно так не сможем

Конечно, так я тебя про это и спрашивал.

Miguel ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

>> Зачем тебе это? В продакшн C++ такое пускать все-равно нельзя. А поиграться - возьми CL, там МП и ООП намного круче.

я уж объяснял зачем — добраться до границ с++ и ощутить...

Есть психонавты, есть алконавты, а это похоже плюсонавт. :)

pathfinder ★★★★
()

ок, попробую убрать костыли с rc и зафрендить потомков.

www_linux_org_ru ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.