From 4227eacd5b88ba1ad191241a1a73cc5d899b0c10 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Wed, 12 Feb 2025 11:16:40 -0500 Subject: [PATCH 1/2] feat(const): updated const to be truly immutable --- src/const/private/const.cpp | 27 +++++++++++++-------------- src/const/public/const.h | 23 +++++++++++++++++------ 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/const/private/const.cpp b/src/const/private/const.cpp index efa8f4b..c1e21b0 100644 --- a/src/const/private/const.cpp +++ b/src/const/private/const.cpp @@ -19,11 +19,7 @@ bool constants::initialize(const std::string& filename) { return load(filename); } -constant constants::get(const std::string& name) { - return constants_[name]; -} - -constant constants::operator[](const std::string& name) const { +constant constants::get(const std::string& name) const { auto it = constants_.find(name); if (it != constants_.end()) { return it->second; @@ -32,6 +28,10 @@ constant constants::operator[](const std::string& name) const { } } +constant constants::operator[](const std::string& name) const { + return this->get(name); +} + bool constants::has(const std::string& name) const { return constants_.find(name) != constants_.end(); } @@ -50,9 +50,9 @@ std::string constants::trim(const std::string& str) { size_t last = str.find_last_not_of(" \t"); return str.substr(first, last - first + 1); } + bool constants::load(const std::string& filename) { std::ifstream file(filename); - std::map constants_temp; if (!file.is_open()) { std::cerr << "Error: Unable to open file " << filename << std::endl; return false; @@ -81,12 +81,12 @@ bool constants::load(const std::string& filename) { // Define exact column widths from Python script int start = 0; - std::string symbol = trim(line.substr(start, col_widths_[0])); start += col_widths_[0]; - std::string name = trim(line.substr(start, col_widths_[1])); start += col_widths_[1]; - std::string valueStr = line.substr(start, col_widths_[2]); start += col_widths_[2]; - std::string unit = trim(line.substr(start, col_widths_[3])); start += col_widths_[3]; // Only trim the unit - std::string uncertaintyStr = line.substr(start, col_widths_[4]); start += col_widths_[4]; - std::string reference = trim(line.substr(start, col_widths_[5])); // Only trim reference + const std::string symbol = trim(line.substr(start, col_widths_[0])); start += col_widths_[0]; + const std::string name = trim(line.substr(start, col_widths_[1])); start += col_widths_[1]; + const std::string valueStr = line.substr(start, col_widths_[2]); start += col_widths_[2]; + const std::string unit = trim(line.substr(start, col_widths_[3])); start += col_widths_[3]; // Only trim the unit + const std::string uncertaintyStr = line.substr(start, col_widths_[4]); start += col_widths_[4]; + const std::string reference = trim(line.substr(start, col_widths_[5])); // Only trim reference // Convert numerical fields safely double value = 0.0, uncertainty = 0.0; @@ -102,11 +102,10 @@ bool constants::load(const std::string& filename) { } // Store in map - constants_temp[symbol] = {name, value, uncertainty, unit, reference}; + constants_.emplace(symbol, constant{name, value, uncertainty, unit, reference}); } file.close(); - constants_ = constants_temp; loaded_ = true; return true; } diff --git a/src/const/public/const.h b/src/const/public/const.h index eae184c..146a221 100644 --- a/src/const/public/const.h +++ b/src/const/public/const.h @@ -11,11 +11,22 @@ * @brief Structure to hold a constant's details. */ struct constant { - std::string name; ///< Name of the constant - double value; ///< Value of the constant - double uncertainty; ///< Uncertainty in the constant's value - std::string unit; ///< Unit of the constant - std::string reference; ///< Reference for the constant's value + const std::string name; ///< Name of the constant + const double value; ///< Value of the constant + const double uncertainty; ///< Uncertainty in the constant's value + const std::string unit; ///< Unit of the constant + const std::string reference; ///< Reference for the constant's value + + /** + * @brief Parameterized constructor. + * @param name The name of the constant. + * @param value The value of the constant. + * @param uncertainty The uncertainty in the constant's value. + * @param unit The unit of the constant. + * @param reference The reference for the constant's value. + */ + constant(const std::string& name, double value, double uncertainty, const std::string& unit, const std::string& reference) + : name(name), value(value), uncertainty(uncertainty), unit(unit), reference(reference) {} /** * @brief overload the << operator for pretty printing @@ -81,7 +92,7 @@ public: * @param key The name of the constant to retrieve. * @return The constant associated with the given key. */ - constant get(const std::string& key); + constant get(const std::string& key) const; /** * @brief Overloaded subscript operator to access constants by key. From 18ce7bf6de3480e19de39ef241d66ef5cc2b8931 Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Wed, 12 Feb 2025 12:53:50 -0500 Subject: [PATCH 2/2] perf(const): const changed to a singelton const needds to be accessed all throughout so it has been changed to a singleton to allow for more efficient usage BREAKING CHANGE: Any previous loads to const will break, also constant->Constant and constants->Constants --- src/const/meson.build | 1 + src/const/private/const.cpp | 43 ++++++++++++--------------- src/const/public/const.h | 52 +++++++++++++++++---------------- src/meson.build | 4 +++ src/resources/const/format.sh | 10 +++++++ src/resources/const/meson.build | 20 +++++++++++++ src/resources/meson.build | 1 + tests/const/constTest.cpp | 34 +++++++++------------ tests/const/meson.build | 2 +- 9 files changed, 96 insertions(+), 71 deletions(-) create mode 100755 src/resources/const/format.sh create mode 100644 src/resources/const/meson.build create mode 100644 src/resources/meson.build diff --git a/src/const/meson.build b/src/const/meson.build index f80d94b..f90b474 100644 --- a/src/const/meson.build +++ b/src/const/meson.build @@ -12,6 +12,7 @@ libconst = library('const', const_sources, include_directories: include_directories('public'), cpp_args: ['-fvisibility=default'], + dependencies: [const_dep], install : true) # Make headers accessible diff --git a/src/const/private/const.cpp b/src/const/private/const.cpp index c1e21b0..08cafde 100644 --- a/src/const/private/const.cpp +++ b/src/const/private/const.cpp @@ -3,23 +3,22 @@ #include #include #include +#include #include + #include "const.h" +#include "embedded_constants.h" // Generated at build time by meson -constants::constants() { - loaded_ = false; +Constants::Constants() { + loaded_ = initialize(); } -constants::constants(const std::string& filename) { - loaded_ = initialize(filename); +bool Constants::initialize() { + return load(); } -bool constants::initialize(const std::string& filename) { - return load(filename); -} - -constant constants::get(const std::string& name) const { +Constant Constants::get(const std::string& name) const { auto it = constants_.find(name); if (it != constants_.end()) { return it->second; @@ -28,15 +27,15 @@ constant constants::get(const std::string& name) const { } } -constant constants::operator[](const std::string& name) const { +Constant Constants::operator[](const std::string& name) const { return this->get(name); } -bool constants::has(const std::string& name) const { +bool Constants::has(const std::string& name) const { return constants_.find(name) != constants_.end(); } -std::set constants::keys() const { +std::set Constants::keys() const { std::set keys; for (const auto& pair : constants_) { keys.insert(pair.first); @@ -44,32 +43,28 @@ std::set constants::keys() const { return keys; } -std::string constants::trim(const std::string& str) { +std::string Constants::trim(const std::string& str) { size_t first = str.find_first_not_of(" \t"); if (first == std::string::npos) return ""; size_t last = str.find_last_not_of(" \t"); return str.substr(first, last - first + 1); } -bool constants::load(const std::string& filename) { - std::ifstream file(filename); - if (!file.is_open()) { - std::cerr << "Error: Unable to open file " << filename << std::endl; - return false; - } +bool Constants::load() { + std::istringstream fileStream(embeddedConstants); std::string line; bool data_section = false; int line_count = 0; - while (std::getline(file, line)) { + while (std::getline(fileStream, line)) { line_count++; // Detect start of data section (double divider line) if (!data_section) { if (line.find("Symbol") != std::string::npos) { // Find header row - std::getline(file, line); // Skip dashed divider - std::getline(file, line); // Skip second dashed divider + std::getline(fileStream, line); // Skip dashed divider + std::getline(fileStream, line); // Skip second dashed divider data_section = true; } continue; @@ -102,10 +97,8 @@ bool constants::load(const std::string& filename) { } // Store in map - constants_.emplace(symbol, constant{name, value, uncertainty, unit, reference}); + constants_.emplace(symbol, Constant{name, value, uncertainty, unit, reference}); } - - file.close(); loaded_ = true; return true; } diff --git a/src/const/public/const.h b/src/const/public/const.h index 146a221..6fe7aa7 100644 --- a/src/const/public/const.h +++ b/src/const/public/const.h @@ -10,7 +10,7 @@ /** * @brief Structure to hold a constant's details. */ -struct constant { +struct Constant { const std::string name; ///< Name of the constant const double value; ///< Value of the constant const double uncertainty; ///< Uncertainty in the constant's value @@ -25,13 +25,13 @@ struct constant { * @param unit The unit of the constant. * @param reference The reference for the constant's value. */ - constant(const std::string& name, double value, double uncertainty, const std::string& unit, const std::string& reference) + Constant(const std::string& name, const double value, const double uncertainty, const std::string& unit, const std::string& reference) : name(name), value(value), uncertainty(uncertainty), unit(unit), reference(reference) {} /** * @brief overload the << operator for pretty printing */ - friend std::ostream& operator<<(std::ostream& os, const constant& c) { + friend std::ostream& operator<<(std::ostream& os, const Constant& c) { os << "<" << c.name << ": "; os << c.value << "±" << c.uncertainty << " "; os << c.unit << " (" << c.reference << ")>\n"; @@ -42,18 +42,28 @@ struct constant { /** * @brief Class to manage a collection of constants. */ -class constants { +class Constants { private: bool loaded_ = false; ///< Flag to indicate if constants are loaded const int col_widths_[6] = {25, 52, 20, 20, 17, 45}; // From the python script used to generate the constants file - std::map constants_; ///< Map to store constants by name + std::map constants_; ///< Map to store constants by name /** - * @brief Load constants from a file. - * @param filename The name of the file to load constants from. + * @brief Default constructor. Private to avoid direct instantiation + */ + Constants(); + + /** + * @brief Load constants from the embedded header file. * @return True if loading was successful, false otherwise. */ - bool load(const std::string& filename); + bool load(); + + /** + * @brief Initialize constants. + * @return True if initialization was successful, false otherwise. + */ + bool initialize(); /** * @brief Trim leading and trailing whitespace from a string. @@ -63,36 +73,28 @@ private: std::string trim(const std::string& str); public: - /** - * @brief Default constructor. - */ - constants(); /** - * @brief Constructor that initializes constants from a file. - * @param filename The name of the file to load constants from. + * @brief get instance of constants singelton + * @return instance of constants */ - constants(const std::string& filename); + static Constants& getInstance() { + static Constants instance; + return instance; + } /** * @brief Check if constants are loaded. * @return True if constants are loaded, false otherwise. */ - bool is_loaded() { return loaded_; } - - /** - * @brief Initialize constants from a file. - * @param filename The name of the file to load constants from. - * @return True if initialization was successful, false otherwise. - */ - bool initialize(const std::string& filename); + bool isLoaded() { return loaded_; } /** * @brief Get a constant by key. * @param key The name of the constant to retrieve. * @return The constant associated with the given key. */ - constant get(const std::string& key) const; + Constant get(const std::string& key) const; /** * @brief Overloaded subscript operator to access constants by key. @@ -100,7 +102,7 @@ public: * @return The constant associated with the given key. * @throws std::out_of_range if the key is not found. */ - constant operator[](const std::string& key) const; + Constant operator[](const std::string& key) const; /** * @brief Check if a constant exists by key. diff --git a/src/meson.build b/src/meson.build index e09becc..4acff3e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,2 +1,6 @@ +# Build resources first so that all the embedded resources are available to the other targets +subdir('resources') + +# Build the main source code subdir('dobj') subdir('const') \ No newline at end of file diff --git a/src/resources/const/format.sh b/src/resources/const/format.sh new file mode 100755 index 0000000..7e53731 --- /dev/null +++ b/src/resources/const/format.sh @@ -0,0 +1,10 @@ +#!/bin/sh +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi +input_file="$1" +output_file="$2" +printf 'const char embeddedConstants[] = R"(' > "$output_file" +cat "$input_file" >> "$output_file" +printf ')";\n' >> "$output_file" diff --git a/src/resources/const/meson.build b/src/resources/const/meson.build new file mode 100644 index 0000000..c36ee07 --- /dev/null +++ b/src/resources/const/meson.build @@ -0,0 +1,20 @@ +data_file = files('const.dat') +command_file = files('format.sh') +output_file = meson.current_build_dir() + '/embedded_constants.h' +message('Data file absolute path: ' + data_file[0].full_path()) +message('Meson source directory: ' + meson.current_source_dir()) +message('Meson build directory: ' + meson.current_build_dir()) + +embedded_constants_h = custom_target('embed_constants', + input: data_file, + output: 'embedded_constants.h', + command: ['sh', '-c', command_file[0].full_path()+' @INPUT@ ' + output_file, '@INPUT@', '@OUTPUT@'] +) + +# Ensure the generated header is included +const_header = include_directories('.') + +const_dep = declare_dependency( + include_directories: const_header, + sources: embedded_constants_h +) \ No newline at end of file diff --git a/src/resources/meson.build b/src/resources/meson.build new file mode 100644 index 0000000..4a8d44e --- /dev/null +++ b/src/resources/meson.build @@ -0,0 +1 @@ +subdir('const') \ No newline at end of file diff --git a/tests/const/constTest.cpp b/tests/const/constTest.cpp index 5bfebe4..7e325ea 100644 --- a/tests/const/constTest.cpp +++ b/tests/const/constTest.cpp @@ -6,7 +6,6 @@ #include #include -std::string RELATIVE_PATH = "../src/resources/const/const.dat"; /** * @file constTest.cpp * @brief Unit tests for the const class. @@ -17,12 +16,8 @@ std::string RELATIVE_PATH = "../src/resources/const/const.dat"; */ class constTest : public ::testing::Test { protected: - constants physicalConstants; - constants initializedConstants; - void SetUp() override { - // Create a DObject with initial data and metadata - constants initializedConstants(RELATIVE_PATH); + Constants::getInstance(); } }; @@ -30,23 +25,22 @@ protected: * @test Verify default constructor initializes correctly. */ TEST_F(constTest, DefaultConstructor) { - EXPECT_NO_THROW(constants()); + EXPECT_NO_THROW(Constants::getInstance()); } /** * @test Verify constructor initializes with provided data and metadata. */ -TEST_F(constTest, ParameterizedConstructor) { - constants obj(RELATIVE_PATH); +TEST_F(constTest, isLoaded) { - EXPECT_NO_THROW(obj.is_loaded()); + EXPECT_NO_THROW(Constants::getInstance().isLoaded()); } /** * @test Verify get method returns the correct constant. */ -TEST_F(constTest, Get) { - constants obj(RELATIVE_PATH); +TEST_F(constTest, GetMethod) { + Constants& obj = Constants::getInstance(); EXPECT_DOUBLE_EQ(obj.get("c").value, 2.99792458e10); EXPECT_EQ(obj.get("c").unit, "cm / s"); EXPECT_DOUBLE_EQ(obj.get("c").uncertainty, 0.0); @@ -56,8 +50,8 @@ TEST_F(constTest, Get) { /** * @test Verify [] opperators returns the correct constant. */ -TEST_F(constTest, Sub) { - constants obj(RELATIVE_PATH); +TEST_F(constTest, SubscriptOperator) { + Constants& obj = Constants::getInstance(); EXPECT_DOUBLE_EQ(obj["c"].value, 2.99792458e10); EXPECT_EQ(obj["c"].unit, "cm / s"); EXPECT_DOUBLE_EQ(obj["c"].uncertainty, 0.0); @@ -67,16 +61,16 @@ TEST_F(constTest, Sub) { /** * @test Verify that the has method returns the correct values */ -TEST_F(constTest, Has) { - constants obj(RELATIVE_PATH); +TEST_F(constTest, HasMethod) { + Constants& obj = Constants::getInstance(); EXPECT_TRUE(obj.has("c")); EXPECT_FALSE(obj.has("c4")); EXPECT_TRUE(obj.has("hbar")); } -TEST_F(constTest, Keys) { - constants obj(RELATIVE_PATH); +TEST_F(constTest, KeysMethod) { + Constants& obj = Constants::getInstance(); std::set checkKeys; checkKeys.insert("c"); checkKeys.insert("wienK"); @@ -104,8 +98,8 @@ TEST_F(constTest, Keys) { } } -TEST_F(constTest, Output) { - constants obj(RELATIVE_PATH); +TEST_F(constTest, StreamOperator) { + Constants& obj = Constants::getInstance(); std::ostringstream os; os << obj.get("c"); diff --git a/tests/const/meson.build b/tests/const/meson.build index 3ba1635..ad88bfe 100644 --- a/tests/const/meson.build +++ b/tests/const/meson.build @@ -11,7 +11,7 @@ foreach test_file : test_sources test_exe = executable( exe_name, test_file, - dependencies: gtest_dep, + dependencies: [gtest_dep, const_dep], include_directories: include_directories('../../src/const/public'), link_with: libconst, # Link the dobj library install_rpath: '@loader_path/../../src' # Ensure runtime library path resolves correctly