7
\$\begingroup\$

Late important updates (unified)

I found, that on my Pi's Debian 12 Bookworm (arm64), there is just an older g++-12 available. Therefore, I am limited to it. Also, this g++ version utilizes -std=c++17 (and so the c++17 tag change) as the highest working standard (so far unverified, trial & error).

Now, I am compiling directly on my Pi using this:

$ g++-12 -std=c++17 -Wall for_review.cpp && ./a.out 

I am sorry, that I could have caused trouble by originally even tagging this question with the c++26 tag, which possibly could have been misleading. I will be more careful in the future.


Original question (shortened)

I did not use C++ for ages, plus I was never much good at it, so please excuse my inexperience. Let me present to you my code running on Linux, reading from a text file, storing value in int for normal (future) operations, and just for show outputting it also properly rounded in float with one (optional, really) unicode char at the end of code.

First, I tested the code on amd64 platform. And then transfered it to Pi. I compiled on Linux Mint 22 without Makefile using:

$ g++-14 -std=c++26 -Wall for_review.cpp; ./a.out 

#include <iostream> #include <iomanip> #include <fstream> int main() { std::string rpi_cpu_temperature_file_name = "/sys/class/thermal/thermal_zone0/temp"; std::ifstream rpi_cpu_temperature_file(rpi_cpu_temperature_file_name); if (!rpi_cpu_temperature_file.is_open()) { std::cerr << "Error while opening file: " << rpi_cpu_temperature_file_name << "\n"; return 1; } // Raspberry CPU temperature value in Celsius degrees, actually an integer in a string; // This would have to be divided by 1000 and rounded to get real value then, e.g. 35.3 std::string rpi_cpu_temperature_raw; if (!getline(rpi_cpu_temperature_file, rpi_cpu_temperature_raw)) { std::cerr << "Error while reading from file: " << rpi_cpu_temperature_file_name << "\n"; rpi_cpu_temperature_file.close(); return 1; } else { rpi_cpu_temperature_file.close(); } // integer representing 2 digits with 3 decimal places, example: 35291 which means 35.3 int rpi_cpu_temperature_int = std::stoi(rpi_cpu_temperature_raw); std::cout << rpi_cpu_temperature_int << "\n"; // Properly rounding output, you may try some direct integer, for example: 35291 = 35.3 std::cout << std::fixed; std::cout << std::setprecision(1); std::cout << rpi_cpu_temperature_int / 1000.0 << " \u00B0" << 'C' << "\n"; return 0; } 
\$\endgroup\$
4
  • \$\begingroup\$Thanks for all of the large feedback to everyone involved, I appreciate it very much. I will try to tweak the accepted solution to my needs. Thanks again!\$\endgroup\$CommentedDec 19, 2024 at 15:12
  • \$\begingroup\$g++ 12.1 on Godbolt (godbolt.org/z/zo7Y9M8os) supports -std=gnu++20, and also -std=gnu++2b with __cplusplus = 202100L (so only preliminary support for C++23, with a date that's only 2021). You can play around with newer compilers on Godbolt, including running for x86 or AArch64. But no debugger so it's not useful for more than playing around.\$\endgroup\$CommentedDec 20, 2024 at 3:33
  • \$\begingroup\$@PeterCordes Hello, morning in here. I was confronted yesterday about this already. And since I tested it quite extensively both on my laptop with Linux Mint 22, and on the Pi4 Debian (arm64) itself, let me repeat that. The problem is in the accepted answer, where the poster used #include <format> which, no matter what I tried, g++-12 failed to load. There is very simple work-around, but I merely pointed out, it is available since g++-14 sadly. (Maybe even in g++-13 which I do not have installed yet. Have a good day and cheers!\$\endgroup\$CommentedDec 20, 2024 at 8:50
  • \$\begingroup\$Ah, yes, although g++ 12 on Godbolt supports -std=gnu++20 (or -std=c++20), its libstdc++ doesn't have complete C++20 support yet, so indeed #include <format> isn't available until G++ 13. Normally GCC versions that recognize something like c++20 instead of just c++2a or 1y or whatever have complete support that that standard, but I was forgetting that might only go for changes to the language itself (like the meaning of keywords, semantics of sequencing, etc.), not necessarily to libraries.\$\endgroup\$CommentedDec 20, 2024 at 8:59

3 Answers 3

7
\$\begingroup\$

This is good use of the appropriate stream:

// line-wrapped for ease of reading std::cerr << "Error while opening file: " << rpi_cpu_temperature_file_name << "\n"; return 1; 

It can be slightly improved by replacing the return value 1 with EXIT_FAILURE (defined in <cstdlib>).


The return 0; at the end of main() is unnecessary, as the same effect is produced by simply running off the end of it.


Consider restructuring the program with two functions - one to read a temperature from file and one to display it. Then we can re-use the code for other temperature sensors.


Instead of reading an integer with std::stoi(), consider converting directly to floating-point with std::stod() - or std::stof(), as single precision is certainly adequate here.

Alternatively, continue reading the millidegrees value as integer, and use integer arithmetic to split it into whole and fractional parts:

 // Add 0.05° to achieve rounding to nearest 0.1° (assume positive temperature) auto divided = std::div(rpi_cpu_temperature_int + 50, 1000); std::cout << divided.quot << '.' << (divided.rem / 100) << "°C\n"; 

Modified code

Here's a version that is more reusable (e.g. for systems with more thermal zones).

#include <cmath> #include <cstdlib> #include <format> #include <fstream> #include <iostream> int read_thermal_zone(unsigned zone) { std::ifstream in; in.exceptions(~std::ios_base::goodbit); in.open(std::format("/sys/class/thermal/thermal_zone{}/temp", zone)); int value; in >> value; return value; } std::ostream& print_millidegrees(std::ostream& os, int value) { // Add 0.05° to achieve rounding to nearest 0.1° value += value < 0 ? -50 : 50; auto divided = std::div(value, 1000); os << divided.quot << '.' << std::abs(divided.rem / 100) << "°C\n"; return os; } int main() { try { auto millidegrees = read_thermal_zone(0); std::cout << millidegrees << '\n'; print_millidegrees(std::cout, millidegrees); } catch (std::ios_base::failure& e) { std::cerr << e.what() << '\n'; return EXIT_FAILURE; } } 
\$\endgroup\$
8
  • 1
    \$\begingroup\$Nitpicking: Should the need arise to print temperature to 2 decimal places, outputting integer values of, for instance, 72.05°C will cause some grief... As said, just a nitpick of some imaginary scenario...\$\endgroup\$
    – Fe2O3
    CommentedDec 19, 2024 at 12:14
  • 1
    \$\begingroup\$@chux - argh, I thought I'd got that right, but my reasoning was faulty. std::div rounds towards zero, not downwards! Thanks for spotting that; I'll fix that shortly.\$\endgroup\$CommentedDec 19, 2024 at 14:31
  • 1
    \$\begingroup\$@Fe2O3, yes, we'd need a setw() call for the fractional part if we were to adapt this to print more than one decimal place. Apologies for taking a short-cut that works only with the single digit of precision.\$\endgroup\$CommentedDec 19, 2024 at 14:36
  • 1
    \$\begingroup\$@chux, agreed that's trickier - willing to leave that as an exercise to the reader, as that level of precision is likely outside the measurement error in this use-case anyway!\$\endgroup\$CommentedDec 19, 2024 at 14:37
  • 2
    \$\begingroup\$Concerning "level of precision is likely outside the measurement error", OP did request "outputting properly rounded float". Further, had prior code used FP and test cases were applied to that and this good answer, a difference would occur. Still I agree with "leave that as an exercise to the reader".\$\endgroup\$
    – chux
    CommentedDec 19, 2024 at 14:43
7
\$\begingroup\$

Don't wear out the keyboard

std::string rpi_cpu_temperature_file_name = ...
vs
std::string fname = ...

There are a number of local variables whose names do not have to be as long as presented.
Don't force the reader to scan across 19 characters searching for the SIGNIFICANT information:
name vs file vs raw vs int.


Streamline the flow

There are 10 lines of code involving an if/else that include duplication of the call to close the open file. Try to simplify this sort of thing, even if it means an additional variable:

 int res = getline( rpi_cpu_temperature_file, rpi_cpu_temperature_raw ); rpi_cpu_temperature_file.close(); // file is closed if( !res ) { std::cerr << "Error while reading from file: " << rpi_cpu_temperature_file_name << "\n"; return 1; } 

Keep KISSing

Consider the final output statement:
std::cout << rpi_cpu_temperature_int / 1000.0 << " \u00B0" << 'C' << "\n";
Commendable is the intent to distinguish three bits appended to the value: a special symbol, that the Celsius scale is used and a newline.
It's slightly unsettling that the C is a character parameter... "Why?"

There are a few variations that would serve, but overlooked may be the compiler's ability to perform string concatenation directly from source code:
std::cout << rpi_cpu_temperature_int / 1000.0 << " \u00B0" "C" "\n";

\$\endgroup\$
0
    6
    \$\begingroup\$

    Naming:

    Personally I find Snake caseword_word_word is more of a Python style. Where Camel casewordWordWord is more C++.
    However, as noted by @Toby Speight and @indi respectively, the C++ standard library uses snake case and the C++ Core Guidelines recommend sticking with existing styles or going with snake case.

    Also your names are rather on the long side. Don't go the other way and make them really small. But usually, you can get the point across in 1 or 2 words rather than a whole sentence.


    Sure, this is fine:

     std::string rpi_cpu_temperature_file_name = "/sys/class/thermal/thermal_zone0/temp"; std::ifstream rpi_cpu_temperature_file(rpi_cpu_temperature_file_name); 

    Personally, I would use:

     std::ifstream file("/sys/class/thermal/thermal_zone0/temp"); 

    You are just checking if the file is open.

     if (!rpi_cpu_temperature_file.is_open()) 

    Other error situations can occur where the file is open but the file is not in a good state. So generally prefer to just test the file for being OK.

     // Simply do this to test if the file is not good. if (!rpi_cpu_temperature_file) 

    Very rarely bother to manually close the file. The object destructor will do that for you.

     { rpi_cpu_temperature_file.close(); } 

    I would do this all in one statement rather than multiple statements.

     std::cout << std::fixed; std::cout << std::setprecision(1); std::cout << rpi_cpu_temperature_int / 1000.0 << " \u00B0" << 'C' << "\n"; 

    #include <iostream> #include <iomanip> #include <fstream> int main() { std::ifstream file("/sys/class/thermal/thermal_zone0/temp"); int tempEncoded; if (file >> tempEncoded) { // Most likely (and important) bit first. double temp = tempEncoded / 1000.0; std::cout << std::fixed << std::setprecision(1) << temp << " \u00B0C\n"; } else { // Handle Errors and messages. return 1; } } 
    \$\endgroup\$
    3
    • 1
      \$\begingroup\$Thought: return 0; inside the if() body (early return on success) would eliminate need for else, allowing simple or complicated handling of errors (without indent). Your thoughts, please...\$\endgroup\$
      – Fe2O3
      CommentedDec 19, 2024 at 10:26
    • 1
      \$\begingroup\$@Fe2O3 I would have no problem with doing that. Programming has a lot of personal preferences or style guide requirements.\$\endgroup\$CommentedDec 19, 2024 at 22:15
    • \$\begingroup\$Does anybody have stats on C++ libraries in Git Hub on what is more common?\$\endgroup\$CommentedDec 19, 2024 at 22:22

    Start asking to get answers

    Find the answer to your question by asking.

    Ask question

    Explore related questions

    See similar questions with these tags.