如何创建、使用自定义的Messenger类?
在C++中,我们可以把一个**类的声明**写进与类名相同的头文件中(className.hh),把**定义**写进cxx文件(className.cxx)中。类的声明格式为
class MyClass : public MotherClass{public:......private:....};
注意:
结尾有一个分号头文件的书写规范,参考这篇文章:http://www.cplusplus.com/forum/articles/10627/精华部分:do nothing if: A makes no references at all to Bdo nothing if: The only reference to B is in a friend declarationforward declare B if: A contains a B pointer or reference: B* myb;forward declare B if: one or more functions has a B object/pointer/reference as a parementer, or as a return type: B MyFunction(B myb);#include "b.h" if: B is a parent class of A#include "b.h" if: A contains a B object: B myb;如和让geant4“认得”我们的类?如何在geant4中使用messenger类?geant4如何知道我们写了一个messenger类?geant4中Messenger类是靠G4UImanager类来管理的。这个类是一个singleton类。什么是singleton?在c++中使用singleton有什么好处?
geant4中使用了大量的singleton类。介绍见 http://en.wikipedia.org/wiki/Singleton_pattern singleton类拥有一个指向本类类型的静态(成员变量)指针,例如fMyClass。
什么是静态成员变量?
一个类的静态成员变量可以通过 MyClass::myStaticVar这样直接引用,它需要在MyClass.cxx文件中所有成员函数定义之外(相当于定义文件全局变量位置)处定义。注意既然是定义,就需要带上类型:
staticVarType MyClass::myStaticVar = myClassStaticInitVal;
这个类的所有对象公用这一个变量,可以通过object.myStaticVar访问该变量。singleton的这个指向本类类型的静态指针fMyClass初值必须赋为NULL。singleton类不允许直接pointer = new SingletonClass或者obj = SingletonClass(),其构造函数为private类型。singleton类会有一个public类型的静态成员方法(即可以通过MyClass::myStaticMethod()方式调用的方法),一般称为GetInstance()`等等。这个方法会检测fMyClass指针是否为空,如果为空则new MyClass。最后反悔fMyClass的值。采用singleton可以很好的保证大型程序中这个类只有一个实例,一般的Service、Manageer、Container类都是singleton的。所以在程序中,我们采用下面的程序来初始化UImanger这个singleton:0
G4UImanager* UI;UI = G4UImanager::GetUIpointer();
在GetUIpointer()中会做这两件事:
fUImanager = new G4UImanager; fUImanager->CreateMessenger();
G4UImanager的构造函数可以看到
64 G4UImanager::G4UImanager()65 : G4VStateDependent(true),66 UImessenger(0), UnitsMessenger(0)
这里G4VStateDependent类是G4UImanager的父类;它的构造函数会调用另一个singleton并且register这个对象(G4UImanager)是否是State相关。构造函数中出现了这一行
treeTop = new G4UIcommandTree("/")
这里G4UIcommandTree类有一个vector名叫tree,用来存放所有的command。
这个类不是singleton,但是因为只有G4UImanager这个singleton会创建G4UIcommandTree这个类,所以整个geant4程序中也只会有一个G4UIcommandTree对象。(推测)UImessenger = new G4UIcontrolMessenger; UnitsMessenger = new G4UnitsMessenger;
在主程序中,我们通过
UI->ApplyCommand("/control/execute run.mac");
这样的命令来运行某个mac文件在ApplyCommand方法中,它首先尝试理解命令字符串
G4String aCommand = SolveAlias(aCmd);
然后通过
G4UIcommand * targetCommand = treeTop->FindPath( commandString );
这句话根据字符串在treeTop(也就是G4UIcommandTree类)找到G4UIcommand对象targetCommand最后执行它:
return targetCommand->DoIt( commandParameter );
那么什么时候我们自己所写的messenger被放进了treeTop的tree这个vector呢?commandTree类有一个成员函数:
void AddNewCommand(G4UIcommand * newCommand);
搜索这个成员函数,在void G4UIcommand::G4UIcommandCommonConstructorCode中被调用过。原来创建一个G4UIcommand类的对象时,调用其构造函数G4UIcommand::G4UIcommand(const char * theCommandPath,...)时,它会调用G4UIcommandCommonConstructorCode (comStr); 这里G4String comStr = theCommandPath;并且被稍加处理。在void G4UIcommand::G4UIcommandCommonConstructorCode中
G4UImanager::GetUIpointer()->AddNewCommand(this);
而在G4UImanager中
void G4UImanager::AddNewCommand(G4UIcommand * newCommand) { treeTop->AddNewCommand( newCommand ); }
所以每一个G4UIcommand都是在创建时写入了G4UImanager的commandTree中。所以在利用G4UImanager调用一个命令时,我们需要先创建这个命令的G4UIcommand对象。
那么,我们是什么时候创建G4UIcommand对象的呢? ** 当我们 new xxxmessenger()时 **我们自己写的messenger类一定是G4UImessenger类的继承类。我们在messenger类中要做两件事:.
创建所有的command对象,要传入类似于"/control/execute"的字符串。
我再在xxxmessenger的构造函数中做这一件事情,例如:
factoryCmd = new G4UIcmdWithAString("/histo/fileName",this); factoryCmd->SetGuidance("set name for the histograms file"); factoryCmd->AvailableForStates(G4State_PreInit,G4State_Idle);
告诉geant4当你执行这个命令时你会进行什么操作。
G4UImanager在ApplyCommand时,最后操作归结到
return targetCommand->DoIt( commandParameter );
通过查看G4command中DoIt函数可以看到最后归结到这句话:
messenger->SetNewValue( this, correctParameters );
而messenger是这样初始化的:
G4UIcommand::G4UIcommand(const char * theCommandPath, G4UImessenger * theMessenger) :messenger(theMessenger),token(IDENTIFIER),paramERR(0)
所以messenger是通过G4UIcommand的构造函数中传入的。在上面的例子中就是this,而最后的工作归结到这个messenger类的SetNewValue成员函数。而在这个成员函数中我们为了实现一些可能需要调用一些Action类的成员函数。例如利用G4UImanager修改事例初级顶点。
利用G4UImanager修改事例初级顶点我们通过在main中 runManager->SetUserAction(new PrimaryGeneratorAction)来读入这个类。在读入的时候我们就为PrimaryGeneratorAction类的对象在内存中分配了空间,并对一些变量赋了(默认的)初值。为了能成功的修改初级顶点的位置,我们需要初级顶点是在什么时候被产生的。我们发现,runManager->Initialize()函数并没有产生初级顶点,在runManager->BeamOn的时候才开始产生顶点。BeamOn函数内容如下:
152 numberOfEventToBeProcessed = n_event;153 ConstructScoringWorlds();154 RunInitialization();155 if(n_event>0) DoEventLoop(n_event,macroFile,n_select);156 RunTermination();
而DoEventLoop如下:
232 InitializeEventLoop(n_event,macroFile,n_select); 233 234 // Event loop 235 for(G4int i_event=0; i_event<n_event; i_event++ ) 236 { 237 ProcessOneEvent(i_event); 238 TerminateOneEvent(); 239 if(runAborted) break; 240 } 241 242 TerminateEventLoop();
这里ProcessOneEvent()内容如下:
261 void G4RunManager::ProcessOneEvent(G4int i_event) 262 { 263 currentEvent = GenerateEvent(i_event); 264 eventManager->ProcessOneEvent(currentEvent); 265 AnalyzeEvent(currentEvent); 266 UpdateScoring(); 267 if(i_event<n_select_msg) G4UImanager::GetUIpointer()->ApplyCommand(msgText); 268 }
而GenerateEvent()包含了这一句:
316 userPrimaryGeneratorAction->GeneratePrimaries(anEvent);
这里的userPrimaryGeneratorAction就是之前SetUserAction时传入的指针。我们调用了GeneratePrimaries()函数,这个函数里面最关键的是这一句话:
particleGun->GeneratePrimaryVertex(anEvent);
在执行这句话之前,GeneratePrimaries()通过
particleGun->SetParticlePosition(G4ThreeVector(xpos, ypos, zpos));
已经设定好了初级顶点的相关参数。所以,为了修改初级顶点的位置,我们需要在BeamOn之前修改PrimaryGeneratorAction类的xpos、ypos、zpos成员变量。
通过前面的只是,我们知道可以通过messenger(例如:PrimaryGeneratorMessenger)的SetNewValue()成员函数来完成任务。但是有几个难点:
什么时候new PrimaryGeneratorMessenger?必须在执行修改的G4UIcommand之前
PrimaryGeneratorMessenger如何取得userPrimaryGeneratorAction指针?
取得指针之后如何修改其xpos、ypos、zpos成员变量?
我们不按顺序来回答这几个问题。
假设我们已经取得了userPrimaryGeneratorAction指针为,我们在PrimaryGeneratorAction类中写三个函数 setXPos()、setYPos()、setZPos(),并且通过userPrimaryGeneratorAction->setXPos()等命令来修改xpos等成员变量。
我们在PrimaryGeneratorMessenger的构造函数中传入PrimaryGeneratorAction的指针:
new PrimaryGeneratorMessenger(PrimaryGeneratorAction* myPGAclass)
为了把userPrimaryGeneratorAction传给PrimaryGeneratorMessenger
一种解决方案是把new PrimaryGeneratorAction()的结果用一个指针保存起来,而不是直接传进runManager->SetUserAction(),并且new PrimaryGeneratorMessenger(myPrimaryGeneratorAction)另一种方法是为PrimaryGeneratorAction增加一个PrimaryGeneratorMessenger类的成员函数,并且在其构造函数中new PrimaryGeneratorMessenger(this);这样,我们就完成了自己的Messenger的部署与使用。