一个问题,两人讨论,几行代码,一些启发
一个问题,两人讨论,几行代码,一些启发By 刘未鹏(pongba)
刘言|C++的罗浮宫(http://blog.csdn.net/pongba)
Shen:请教一个问题,我简化一下,发给你:
Pongba:OK
Shen: 就这样发给你吧,不多,我描述一下:
template<class T> class A { };
class B { public: A * m_task; };
class C: public B
{
public:
C();
C(A*);
};
typedef A<C> D;
C::C(A* p):m_task(p){};
定义了这几个类。
C::C(A * p) : m_task (p) 这句,是编译不过的,因为A是定义的模板类,该如何写?
Pongba: 当然不通过啊。你没给模板参数啊,A是模板类,要模板参数啊。
Shen: 是的,但是我写 C::C(D * p): m_task ( p ) 也是不对,似乎总是循环的,这样的定义的有问题,因为A<C>这时候C的构造函数中做,但是C还没有构造出来。
Pongba: 你要考虑的是你想表达的概念,类只是一个手段;首先明确这三个类的语意,而不是语法。
Shen: typedef A<C> D; 这里,我都多个不同的业务,所以定义不同的C。
Pongba: A的语意是什么?
Shen: A是个基类,D是A的不同业务,比如D1,D2,...
Pongba: -_-||A当然是基类,我想知道的是A的目的是什么,A为什么有一个模板参数。这个模板参数放在那儿的初衷是什么。
Shen: class B { public: A * m_task; } 所以B中,有这样的定义。是希望通过C::C(A * p) : m_task(p) ,将B中的m_task指向A(当然是A的不同业务)。A的模板参数的原因,是因为我希望后面typedef A<C> D;
Pongba: B的成员不应该由C初始化,破坏了封装原则。
Shen: 也许还有C1 ,C2 ,所以希望 typedef A<C1> D1; typedef A<C2> D2; 因为有多个C,(如C1,C2,C3...)
Pongba: 问题是为什么你希望typedef A<C> D; 我想知道的是A是个什么类。他的功能是什么。这么说吧,STL的vector,他的语意是“一组元素”,他为什么要一个模板参数,是因为要“参数化”这组元素的类型。那么A的语意是什么,他为什么要一个模板参数。
Shen: A的作用是,有virtual函数,调用不同的业务。
Pongba: A是“业务”类吗?
Shen: 错了,B是业务基类,刚才说错了。C是从B继承的,因为有多个C业务,比如C1,C2...
Pongba: 好,B是业务基类,所有从B继承的都是具体的业务类,Ci。这个理解对吧?
Shen: right
Pongba: 那么,还是那个问题,A的作用呢?是“调用业务基类”吗?也就是说A的“职责”是什么。先把语言放在一边,考虑你的设计。
Shen: A的作用主要是线程处理。我再解释一下:A的作用,是把B的业务放入队列,取出处理等操作。至于怎么处理,就是B处理了,A只负责入出队列等操作。A有自己的队列和定时器等。
Pongba: 也就是说,A是一个任务调度器。只负责调度逻辑。对否?
Shen: 对。
Pongba: 好,那下一个问题。B里面为何要指向A。
Pongba: 哦,等等,先回答这个问题:A里面的那个“队列”是如何实现的。
Shen: 唉,这个就是前人设计的不好了:
Pongba: 是异构队列吗?
Shen: 队列倒是很独立。
Pongba: 异构的意思就是,能存放不同的派生自B的任务吗?比如C1,C2…
Shen: 与这个地方没有关系。
Pongba: 这么说吧,A的模板参数T,在哪个地方被用到了?
Shen: 我还没有说完:前人设计的不好的原因是:A队列中放的是B。B中有:m_task->enqueue( this )。你可以把m_task看做一个队列处理的东东。
Pongba: 晕,也就是说一个task创建起来是自动enque的,不早说:)
Shen: 是的,所以这个是以前代码的风格不好之处。
Pongba: m_task->enqueue( this ) 是发生在B的构造函数中的没错吧
Shen: 非也,在一个普通的函数中。普通的成员函数。
Pongba: 嗯,理解。必要的时候才放入调度队列。是吧。让用户有手动调用任务对象的某个函数将它放入队列的自由。
Shen: 是的。
Pongba: 那还是回答刚才那个问题: A的模板参数T,在什么地方被用到了?
Shen: C::C(A * p) : m_task(p) 就是这里,用错了,不知道该如何用。
Pongba: 唉,我猜你误会原有代码的结构了。你翻开A类的代码。
Shen: B中,应该知道task是哪种TASK
Pongba: 找到A里面什么地方用到了模板参数T。如果不出意外因该是类似于 std::vector<T*> v;。
Shen: 原来没有,是最近增加了业务。所以准备用模板,简化一下,不要写多个业务处理类。
Pongba: 也就是说这个模板参数是你自己加上去的?
Shen: right。
Pongba: 那首先你要明白用模板的意义。你准备用模板之前的动机是什么?也就是说,需求是什么?或者说,你面临了什么问题,才想要用模板的。
Shen: 主要这段,有循环嵌套使用的地方,所以不好处理。
Pongba: 循环嵌套,是有深层原因的。不是语法问题。是设计问题。所以我现在正试图用问问题的方法来帮你找出根本原因。
Shen: 是的,设计的时候没有考虑细节。
Pongba: 不是,是设计的时候没有在抽象层考虑好就一下潜入到语言层面。这样的问题在论坛上很常见,尤其是遇到模板的时候。我现在需要你提供更多的上下文。如果我猜得不错,A里面应该有一个B*的容器。这是一个异构容器,里面可以存放所有派生自B的类的对象。
Shen: 唉,实际开发中,一般设计和开发都是分开的:) 我设计别人写,别人设计我写。
Pongba: 用一段简单的代码表达你当时面临的问题。
Shen: 我来写一下,你等一会,大概要一段时间,我把业务逻辑去掉。
Shen: 我去掉业务后,代码如下:
template<class T> class A{ int enqueue(B * p) { //放入队列 } //另一个线程,取出队列中的数据p,然后 p->process(); }
class B { public: A * m_task; m_task->enqueue( this ); virtual process(); }
class C: public B { public: C(); virutal process() { //自己的业务 } }
Pongba: 那现在有什么问题呢,为什么要吧A做成模板呢?
Shen: 因为C有多个,所以 typedef A<C> D; typedef A<C1> D1; typedef A<C2> D2; 因为不同的C,希望不同的A,所以typedef A<C> D;
Pongba: C有多个有什么问题吗?只要都继承自B,就都可以放到A里面。你程序里面是不是有一个全局的A对象?
Shen: 错了。
Pongba: 问题是,不同的C为什么希望要不同的A?
Pongba: 不同的C是可以放到同一个A对象中的。因为他们都继承自B,而A对象里面是一个保存B*的容器。现在,你回忆一下,最初的问题是什么,也就是说,干嘛想起来要去把A模板化。难道就是因为“不同的C”?A本来就可以处理“不同的C”啊,别忘了。
Shen: 似乎有点眉目。
Pongba: A内部本就是一个异构容器,所有不同的C只要继承自B,都可以放在A里面。
Shen: 我来看看设计文档,当时为什么定义成多个A。
Shen: 实际应用中A是处理线程的,D1,D2,D3分别是处理CDMA,GPRS,ADSL的。我来看看当时设计的时候,怎么考虑的。所以写了这句:typedef A<C> D;
Pongba: 是不是说,CDMA,GPRS,ADSL分别是三类不同的任务?
Shen: D1,D2,D3都是typedef A<C> D; 为了起线程和队列的。但是线程和队列与业务无关,为什么要typedef A<C> D;?
Pongba: typedef A<C> D;是谁写的?是不是说,CDMA ,GPRS, ADSL分别是三类不同的任务?
Shen: 设计的时候写的,所以我在理解:)
Pongba: “C”在文档中的实际名字是什么?文档中写的就是“typedef A<C> D”吗?
Shen: 当然不是:) 呵呵,我是为了简化,所以这么写的。CADSLBillMsg。
Pongba: 那还不给我实际的名字,名字有含义的呀老大。
Shen: 这个是C的一个名字:)
Pongba: 唉… 忙活半天,这不是简化,是丢失信息啊。
Shen: 写名字,太长,不容易理解
Pongba: …
Pongba: CADSLBillMsg是不是仍然是用作基类的。你说有C1,C2,C3,是不是个代表CDMA,GPRS,ADSL这三种任务?比如CADSLBillMsg是所有ADSL任务的基类?对不对?
Shen: 发给你吧,会看的清楚一些:)
Pongba: 好吧。
Shen:
template<class T> class CBillTask
{
int enqueue(B * p)
{
// 放入队列
}
// 另一个线程,取出队列中的数据p,然后p->process();
}
class CBillMsg
{
public:
CBillTask * m_task;
m_task->enqueue( this );
virtual process();
}
class CADSLBillMsg: public CBillMsg
{
public:
CADSLBillMsg();
virutal process()
M
页:
[1]