3.5. Standard Library Containers
Table of Contents
`std::vector`
In Geant4 applications you will often need to store lists of hits, positions, energies, or pointers to objects. The most common C++ container for this is std::vector. A std::vector<T> is a dynamic array of elements of type T. It grows automatically when you add elements and gives fast access to elements by index.
A typical declaration looks like:
std::vector<G4double> energies;
std::vector<G4ThreeVector> positions;
std::vector<G4int> hitIDs;
You add elements with push_back:
energies.push_back(1.23 * MeV);
positions.push_back(G4ThreeVector(0., 0., 10.*cm));
You access elements by index with operator[]:
G4double e0 = energies[0];
G4ThreeVector pos_last = positions[positions.size() - 1];
size() returns the current number of elements, and empty() tells you whether there are any elements at all:
if (!energies.empty()) {
G4cout << "Stored " << energies.size() << " energy values" << G4endl;
}If you know in advance roughly how many elements you will store, you can improve performance by reserving capacity:
energies.reserve(1000);This allocates memory for at least 1000 elements and avoids repeated reallocations while pushing elements.
To remove all stored elements but keep the allocated memory for reuse, call:
energies.clear();You can store pointers in vectors as well, for example pointers to hit objects or user-defined classes. For raw pointers:
std::vector<MyHit*> hits;
hits.push_back(new MyHit(...));You must then delete these objects yourself at the right time, typically in a hit collection or at the end of an event.
To simplify memory management, modern C++ often uses smart pointers, for example:
#include <memory>
std::vector<std::unique_ptr<MyHit>> hits;
hits.push_back(std::make_unique<MyHit>(...));The objects will be deleted automatically when the vector goes out of scope or when you clear it.
In Geant4 user code, never access a vector element using operator[] with an index that is greater than or equal to size(). This is undefined behavior and can cause crashes that are difficult to debug.
std::vector is used in many Geant4 classes internally, and you will encounter it in hit collections, analysis objects, and your own data structures, so it is important to be comfortable declaring, filling, and reading from vectors.
Strings
Text information is often needed in Geant4 for names of volumes, materials, particles, and for file names or messages. C++ provides std::string for this purpose.
A simple string declaration and initialization looks like:
#include <string>
std::string detectorName = "MyCalorimeter";
std::string fileName = "output.root";
You can concatenate strings with the + operator:
std::string fullName = detectorName + "_layer1";To inspect or compare strings:
if (detectorName == "MyCalorimeter") {
// Do something specific
}
std::size_t length = detectorName.size();
char firstChar = detectorName[0];Strings are often used when constructing Geant4 objects that take a name:
auto logicDetector = new G4LogicalVolume(
solidDetector,
detectorMaterial,
detectorName // std::string is implicitly converted to const char*
);
You can also construct strings from numbers using streams or utility functions such as std::to_string:
for (G4int i = 0; i < 10; ++i) {
std::string volName = "Tile_" + std::to_string(i);
// Use volName as the name for this tile
}
Geant4 uses its own output stream types G4cout and G4cerr, but they work with std::string in the same way as standard C++ streams:
G4cout << "Creating volume " << detectorName << G4endl;Iterators
Standard library containers, including std::vector, use iterators to traverse their contents. An iterator behaves like a pointer that moves through the elements of a container. Understanding iterators is important for reading and manipulating collections of data in Geant4, for example hits stored in std::vector, or analysis objects stored in containers.
For a vector, you obtain iterators with begin() and end():
std::vector<G4double> energies;
// ... fill energies ...
for (auto it = energies.begin(); it != energies.end(); ++it) {
G4double e = *it;
// Process e
}
Here it is an iterator, and *it gives you a reference to the element it points to.
Modern C++ provides range based for loops, which are simpler and internally use iterators:
for (auto e : energies) {
// e is a copy of each element
}
for (auto& e : energies) {
// e is a reference, you can modify the element
e *= 0.5;
}You can use iterators with containers of user-defined types or pointers:
std::vector<MyHit*> hits;
// ... fill hits ...
for (auto hit : hits) {
if (hit) {
hit->Print();
}
}
In many Geant4 examples you will see iterator based loops over collections, especially when working with hit collections, ntuple rows, or other STL based containers inside user code. The general pattern is always the same: get begin() and end(), advance an iterator from one to the other, and dereference it to access the current element.
When using iterators in Geant4 user code, do not modify the container (such as adding or removing elements) while iterating over it with iterators that were obtained before the modification, unless the C++ standard explicitly allows it for that operation. This can invalidate iterators and lead to undefined behavior.
By using std::vector, std::string, and iterators correctly, you can build clear and efficient data structures that work smoothly with Geant4 classes and examples.
Views: 9
KAHIBARO