Возникла такая проблема (см. код ниже). Необходимо доопределить метод run() в классе BaseThread в наследнике класса Base классе ConcreateClass. Первая идея которая у меня возникла, просто обратиться к методу класса Base переопределенному в ConcreateClass. Так и буду пока делать. Но теперь у меня академический интерес, какие еще могут быть способы решения.
#include <iostream>
using namespace std;
//////////////////////////////////////////////////////////////////////////
// I can't change this class.class Thread
{
public:
virtual ~Thread() {}
void start()
{
run();
}
private:
virtual void run()
{
cout << "Thread::run()" << endl;
}
};
//////////////////////////////////////////////////////////////////////////class Base
{
public:
void start()
{
baseThread.start();
}
protected:
class BaseThread: public Thread
{
public:
void someUsefulStuff()
{
cout << "BaseThread::someUsefulStuff()" << endl;
}
};
BaseThread baseThread;
};
class ConcreateClass: public Base
{
public:
void useStuff()
{
baseThread.someUsefulStuff();
}
private:
//////////////////////////////////////////////////////////////////////////
// As example what I want to receive.class BaseThread: public Thread
{
void run()
{
cout << "ConcreateClass::BaseThread::run()" << endl;
}
};
//////////////////////////////////////////////////////////////////////////
};
int main()
{
ConcreateClass concreateClass;
concreateClass.start();
concreateClass.useStuff();
return 0;
}
Здравствуйте, Acteon, Вы писали:
A>Добрый день.
A>Возникла такая проблема (см. код ниже). Необходимо доопределить метод run() в классе BaseThread в наследнике класса Base классе ConcreateClass. Первая идея которая у меня возникла, просто обратиться к методу класса Base переопределенному в ConcreateClass. Так и буду пока делать. Но теперь у меня академический интерес, какие еще могут быть способы решения.
То есть: у Base член baseThread имеет тип BaseThread, но у ConcreteClass тот же самый член должен иметь фактический тип, скажем, ConcreteThread.
Подходы здесь следующие.
1) Вместо inplace сделать указатель — и создавать в конструкторе финального класса нужный объект
class Base
{
shared_ptr<BaseThread> m_thread;
public:
explicit Base(shared_ptr<BaseThread> thread = shared_ptr<BaseThread>(new BaseThread())
{
.....
}
void useThread() { m_thread->doUsefulStuff(); }
};
class Concrete : public Base
{
public:
Concrete() : Base(shared_ptr<BaseThread>(new ConcreteThread()))
{
.....
}
};
2) Развитием этой темы является виртуальное наследование
class ThreadHolder
{
protected:
shared_ptr<BaseThread> m_thread;
explicit ThreadHolder(shared_ptr<BaseThread> thread) : m_thread(thread) {}
};
class Base : public virtual ThreadHolder
{
public:
Base(X,Y,Z) : ThreadHolder(shared_ptr<BaseThread>(new BaseThread()))
{
.....
}
};
class Concrete : public Base // и, соответственно, public virtual ThreadHolder
{
public:
Concrete(T,U) : ThreadHolder(shared_ptr<BaseThread>(new ConcreteThread())) // виртуальная база конструируется вперёд остальных!
, Base(x,y,z) // конструктор базы обойдёт вызов конструктора виртуальной базы
{
.....
}
};
3) Хотя можно и с обычным наследованием обойтись — но с оверхедом
class Base
{
protected:
virtual BaseThread& thread() { return m_thread; }
// обещанный оверхед
BaseThread m_thread;
public:
void useThread() { thread().doUsefulStuff(); }
.....
};
class Concrete : public Base
{
protected:
virtual ConcreteThread& thread() /*override*/ { return m_thread; }
ConcreteThread m_thread;
.....
};
4) либо с двухфазной инициализацией
class Base
{
shared_ptr<BaseThread> m_thread;
protected:
virtual shared_ptr<BaseThread> makeThread() { return shared_ptr<BaseThread>(new BaseThread()); }
public:
void postInit() { m_thread = makeThread(); } // вызвать у полностью сконструированного класса!void useThread() { m_thread->doUsefulStuff(); }
.....
};
class Concrete : public Base
{
protected:
virtual shared_ptr<BaseThread> makeThread() /*override*/ { return shared_ptr<BaseThread>(new ConcreteThread()); }
.....
};
5) Шаблоны
template<class ThreadType>
class BaseT
{
ThreadType m_thread;
public:
Base() { ..... }
......
};
class Base : public BaseT<BaseThread> {};
class Concrete : public BaseT<ConcreteThread>
{
.....
};
6) Развитием темы шаблонов будет CRTP
template<class Final>
class BaseT
{
/*abstract: m_thread;*/
Final* myself() { return static_cast<Final*>(this); }
public:
void useThread() { myself()->m_thread.doUsefulStuff(); }
.....
};
class Base : public BaseT<Base>
{
friend class BaseT<Base>;
BaseThread m_thread; /*implement*/
.....
};
class Concrete : public BaseT<Concrete>
{
friend class BaseT<Base>;
ConcreteThread m_thread; /*implement*/
.....
};
7) А ещё, можно отказаться от разведения классов xxxxxThread. Возьмём boost::shared_ptr<boost::thread> и подсунем ему нужное замыкание.
Причём можно сделать несколько вещей
— создание объекта потока в финальном классе — аналогично (1), (2) или (4); только вместо new ConcreteThread будет bind(&Concrete::threadfunc,this) или что угодно своё
— создание объекта потока в базовом классе и пауза, а дальше как обычно, паттерн Шаблонный метод, вызов виртуальных функций (имхо, это хак — поскольку фактический тип объекта меняется по ходу выполнения функции)
class Base
{
bool m_passed;
boost::barrier m_gate;
boost::thread m_thread;
void waitForStart() // рабочий поток ждёт
{
assert(!m_passed);
m_gate.wait();
assert(m_passed);
}
void letOff() // ведущий поток запускает рабочего
{
if(m_passed) return; // либо assert(!m_passed)
m_passed = true;
m_gate.wait();
}
void threadEntry()
{
waitForStart();
threadBody(); // в этом месте vfptr уже выставлен на финальный класс
}
virtual void threadBody() { ..... }
public:
Base() : m_passed(false), m_gate(2), m_thread(boost::bind(&Base::threadEntry,this)) {}
void init() { letOff(); } // вызвать после создания финального класса
};
class Concrete : public Base
{
virtual void threadBody() /*override*/ { ...... }
};
Перекуём баги на фичи!
Re[2]: Доопределение методов подклассов при наследовании
Подходы 1, 2 подходят. Единственное, что мне не нравится в подходе 2, это мои слабые знания. Т.е. я никогда еще не пользовался виртуальным наследованием. Возможно, и мои коллеги тоже. Такой подход требует хорошей документации исходников. Кстати, как часто вам приходилось им пользоваться?
В подходе 3 минусом можно считать, что объект класса Concrete будет содержать в себе также объект класса BaseThread, но не пользоваться им. Получается перерасход ресурсов. Опять же, никогда не пользовался переопределением свойств классов. Что вы можете сказать на этот счет, плохо это, или хорошо? Какие могут быть проблемы, при таком использовании этой техники (ну кроме потребления ресурсов)?
Подход 4. Как минус можно назвать усложнение процесса инициализации объекта. Т.е. нужны хорошие комментарии.
Подход 5. В общем подходит очень хорошо.
Подход 6. Потерялось наследование Concreate от Base. А зачем класс Concrete дружит с классом friend class BaseT<Base>; ? И кстати, зачем вообще объявлена дружба с классом, от которого мы наследуемся?
Подход 7. В бусте потоки в связке с биндерами очень мощные. Но к сожалению, я вынужден использовать Qt для этих целей. Работаем с тем, что есть, а не с тем чем хочется.
В общем пока примерял все подходы, родилась еще одна идея. Унаследовать класс Base от класса Thread. Получилось что-то на подобие вот этого здесь Критика приветствуется.
P.S. Осознал, что наследование должно быть private, но менять уже не буду.
Re[3]: Доопределение методов подклассов при наследовании
Здравствуйте, Acteon, Вы писали:
A>Подход 6. Потерялось наследование Concreate от Base.
Не потерялось. Base и Concrete — финальные классы.
Если хочется сделать наследование в духе CRTP, то нужно делать так
template<class Final> class BaseT // <class Final> - аналог vfptr у обычного наследования
{
void foo() { static_cast<Final*>(this)->bar("hello"); } // косвенное обращение к самому себе - аналог виртуального вызоваvoid bar(const char* s) {}
.....
};
class Base : public BaseT<Base> {}; // финальный класс для данного шаблонаtemplate<class Final> class DescendantT : public BaseT<Final>
{
int bar(string s, int n=123) {} // перекрытие, причём в весьма свободной форме
.....
};
class Descendant : public DescendantT<Descendant> {};
template<class Final> class FarDescendantT : public DescendantT<Final>
{
// если перекрытия нет, то заимствуем из базового класса
};
class FarDescendant : public FarDescendantT<FarDescendant> {};
A>А зачем класс Concrete дружит с классом friend class BaseT<Base>; ? И кстати, зачем вообще объявлена дружба с классом, от которого мы наследуемся?
Затем, что перегружаемый член m_thread (к которому обращается базовый класс) засунут в private:
A>Подход 7. В бусте потоки в связке с биндерами очень мощные. Но к сожалению, я вынужден использовать Qt для этих целей. Работаем с тем, что есть, а не с тем чем хочется.
Но хотя бы boost:: / std::tr1:: bind можно использовать?
Всегда же можно сделать санки (thunk) в ООП-стиль.
И не рожать класс на каждый новый вид потока, а конструировать из шаблона прямо на месте.
A>В общем пока примерял все подходы, родилась еще одна идея. Унаследовать класс Base от класса Thread. Получилось что-то на подобие вот этого здесь Критика приветствуется.
Ну, в принципе, так нередко делают. Когда объект — сам себе представитель потока (или, кстати, представитель окна).
Есть два подхода к архитектуре.
— с иерархией тяжеловесных объектов, реализующих поведение потоков, окон, и т.п.
— с единообразными хэндлами и разнообразными объектами-владельцами, не обязательно связанными в одну иерархию.
Для Qt, как и для многих ООП-фреймворков, характерно первое.
Тут надо смотреть на цену этого склеивания. Потому что потом расклеить объект на поток и всё остальное будет мучительно.
Но если расклеивать не собираешься, то почему бы и нет?
Перекуём баги на фичи!
Re[4]: Доопределение методов подклассов при наследовании
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, Acteon, Вы писали:
A>>Подход 6. Потерялось наследование Concreate от Base. К>Не потерялось. Base и Concrete — финальные классы.
Все, увидел. Оно просто реализуется через myself(). Я правильно понял?
A>>А зачем класс Concrete дружит с классом friend class BaseT<Base>; ? И кстати, зачем вообще объявлена дружба с классом, от которого мы наследуемся?
К>Затем, что перегружаемый член m_thread (к которому обращается базовый класс) засунут в private:
Да, увидел, понял. А то что Concrete дружит с классом BaseT<Base> вместо BaseT<Concrete> просто ошибка.
A>>Подход 7. В бусте потоки в связке с биндерами очень мощные. Но к сожалению, я вынужден использовать Qt для этих целей. Работаем с тем, что есть, а не с тем чем хочется.
К>Но хотя бы boost:: / std::tr1:: bind можно использовать?
Нет. Проект уже давний, на чем пишут, на том пишут. Но уже раз десять сталкивался с тем, что приходилось писать велосипеды, т.к. в Qt нет того, что есть в бусте. Даже элементарный non_copyable реализован не так как надо.
К>Всегда же можно сделать санки (thunk) в ООП-стиль. К>[c] К>template<class Fun> struct FunThread: Thread \\ Ошибочка. К>{ К> Fun m_fun; К> explicit FunThread(Fun fun) : m_fun(fun) {} К> virtual void run() /*override*/ { m_fun(); } К>};
И получится уже что-то близкое к бустовским потокам. Это уже 11-й раз. За идею спасибо.
A>>В общем пока примерял все подходы, родилась еще одна идея. Унаследовать класс Base от класса Thread. Получилось что-то на подобие вот этого здесь Критика приветствуется.
К>Ну, в принципе, так нередко делают. Когда объект — сам себе представитель потока (или, кстати, представитель окна). К>Есть два подхода к архитектуре. К>- с иерархией тяжеловесных объектов, реализующих поведение потоков, окон, и т.п. К>- с единообразными хэндлами и разнообразными объектами-владельцами, не обязательно связанными в одну иерархию. К>Для Qt, как и для многих ООП-фреймворков, характерно первое.
К>Тут надо смотреть на цену этого склеивания. Потому что потом расклеить объект на поток и всё остальное будет мучительно. К>Но если расклеивать не собираешься, то почему бы и нет?
Ясно, спасибо. Разъединять их в будущем не собираюсь. Оставим будущие проблемы — потомкам.
Re[2]: Доопределение методов подклассов при наследовании
Здравствуйте, Acteon, Вы писали:
A>>>Подход 6. Потерялось наследование Concreate от Base. К>>Не потерялось. Base и Concrete — финальные классы.
A>Все, увидел. Оно просто реализуется через myself(). Я правильно понял?
Ага. Это, чтобы уменьшить количество писанины — static_cast<Final*>(this) всюду не вставлять.
A>Да, увидел, понял. А то что Concrete дружит с классом BaseT<Base> вместо BaseT<Concrete> просто ошибка.
Дык, я "с листа" писал, поторопился
К>>Но хотя бы boost:: / std::tr1:: bind можно использовать?
A>Нет. Проект уже давний, на чем пишут, на том пишут. Но уже раз десять сталкивался с тем, что приходилось писать велосипеды, т.к. в Qt нет того, что есть в бусте. Даже элементарный non_copyable реализован не так как надо.
В смысле, пишут для старого компилятора?
Прицепить бустовские header-only библиотеки можно безболезненно к чему угодно.
К>>template<class Fun> struct FunThread: Thread \\ Ошибочка.
И снова поторопился
A>И получится уже что-то близкое к бустовским потокам. Это уже 11-й раз. За идею спасибо.
Можно не тащить весь boost::bind, а нашлёпать заготовки биндингов самых расхожих. Ну там,
(void(*)()) — из свободной функции без параметров
(void(*)(Arg),Arg) — из функции с одним параметром
(void(Class::*)(),Class*) — из функции-члена класса
(void(Class::*)(Arg),Class*,Arg) — из функции-члена с дополнительным параметром
Чтобы потом потоки рожать сразу на месте, без классов.
template<class Fun, class Arg> struct bind_fun1_t
{
Fun m_fun;
Arg m_arg;
bind_fun1_t(Fun fun, Arg arg) : m_fun(fun), m_arg(arg) {}
void operator()() { m_fun(m_arg); }
};
template<class Obj, class Mem> struct bind_mem0_t
{
Obj* m_obj;
Mem m_mem;
bind_mem0_t(Obj* obj, Mem mem) : m_obj(obj), m_mem(mem) {}
void operator()() { (m_obj->*m_mem)(); }
};
template<class Obj, class Mem, class Arg> struct bind_mem1_t
{
Obj* m_obj;
Mem m_mem;
Arg m_arg;
bind_mem0_t(Obj* obj, Mem mem, Arg arg) : m_obj(obj), m_mem(mem), m_arg(arg) {}
void operator()() { (m_obj->*m_mem)(arg); }
};
template<class F> F bindf(F f) { return (f); } // тривиальноtemplate<class F, class A> bind_fun1_t<F,A> bindf(F f, A a) { return bind_fun1_t<F,A> (f,a); }
template<class O, class M> bind_mem0_t<O,M> bindm(O*o, M m) { return bind_mem0_t<O,M> (o,m); }
template<class O, class M, class A> bind_mem1_t<O,M,A> bindm(O*o, M m, A a) { return bind_mem1_t<O,M,A>(o,m,a); }
Перекуём баги на фичи!
Re[6]: Доопределение методов подклассов при наследовании
Здравствуйте, Кодт, Вы писали:
К>В смысле, пишут для старого компилятора? К>Прицепить бустовские header-only библиотеки можно безболезненно к чему угодно.
Проблемы скорее организационные. Enviroment для проекта весьма не тривиальный. Что бы убедить добавить в него еще и буст, мне нужны веские аргументы. Их не то что бы нет, на цифры положить их сложно. И если совсем уж откровенно, C# может превратить мозги "сишника" в кашу. Тот же эффект можно получить используя повсеместно Qt. У меня нет знакомых "кютишников" которые потом начали использовать буст. Загвоздка может быть еще и в этом (как всегда, в умах).