0% found this document useful (0 votes)
26 views8 pages

Master C++ 11

Uploaded by

Kirito Senpai
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF or read online on Scribd
0% found this document useful (0 votes)
26 views8 pages

Master C++ 11

Uploaded by

Kirito Senpai
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF or read online on Scribd
You are on page 1/ 8
Learn Modern C++ Discover a language matched to today’s computing needs Modern C++ 101: Refactoring Legacy Code Posted on December 5, 2023December 6, 2023 by cpptutor So here’s a program written in a hurry, possibly in the style of a college CS student in about week six of learning C++ as a taught course. It's simple, it works, and it can (naturally) be improved upon: 1| #include 2| #include 3 4| class Circle { 5 static double PI; 6 double radius; 7| public: 8 Circle(double r) { 9 radius = r; 10 } 11 double circumference() { 12 return 2.0 * PI * radius; 13 } 14 double area() { 1s return PI * pow(radius, 2.0); 16 } 7) ys 18 19 | double Circle::PI = asin(1.@) * 2.0; 20 21 | int main() { 22 Circle a(2.0), b(18), ¢(1.@F); 23 std: :cout.precision(15) ; 24 std::cout << "a.circumference(): " << a.circumference() << '\n'; 25 << "acarea(): " << a-area() << '\n'5 26 << "b.circumference(): " << b.circumference() << ‘\n's 27 << "b.area(): " << bearea() << "\n"5 28 << "c.circumference(): " << c.circumference() << ‘\n's 29 << "c.area(): " << C.area() << ‘\n"5 3@| } In the code above, the Circle class defines private two members in lines 5 and 6, the first PI being a static member (sometimes called a class variable in other languages), and the second radius being a regular member. The storage for PI_ has to be allocated at a single point in the executable, which happens at line 19 where its value is also assigned. To avoid link-time errors, in a real project this line (only) would reside in a separate source file Circle.cpp while the rest of the class definition would reside in Circle.hpp , facts which may cause surprise to novice C+ coders. The constructor at lines 8-10 initializes radius with its single parameter, and there are two other member functions called area() and circumference() at lines 11-16, both of which take no parameters and returna double . (It is double-precision arithmetic which is used throughout.) The example main program shown above produces the following output: -circumference(): 12.5663706143592 -area(): 12.5663706143592 -circumference(): 62.8318530717959 -area(): 314.159265358979 -circumference(): -6.2831853@717959 -area(): 3.14159265358979 This article aims to explore some of the more modern C++ styles which can be applied to this code, causing as few changes as necessary to the client (user) code in main() . Use of inline static To get around the need for two source files due to the use of a_ static class data member, it is possible touse the inline keyword in addition to static : class Circle { inline static double PI = asin(1.0) * 2.0; Wives Expose useful constant data Other parts of the program might want to use a calculated value of PI (as Circle: :PI ). Declaring it public allows this, but runs the risk of it being changed, affecting all subsequent calls of the member functions which use it. The solution which caters for accidental/malicious access, thread safety etc. is to declare this value constexpr (at best, if possible), or const (at worst): class Circle { inline static const double PI = asin(1.0) * 2.8; // check if your system has ¢ Const-correctness Member functions which do not change any member variables should be declared with const , and this can be applied to the return value, too: const double area() const { return PI * pow(radius, 2.0); + Don’t call functions unnecessarily Since pow() isa C Standard Library function, we are probably guaranteed a non-inline function call overhead. However it is not necessary to call it at all, just use: return PI * radius * radius; Part of the reason an exponent operator has never been added to the C++ language is apparently that in real code, many uses of pow() were for low integral indices. Provide access to member data What happens if we make a Circle() object and need its radius at a later point in the program? The worst solution would be to make radius public. A better solution is to return it from a member function with a similar name (note: not identical as this is not permitted). From the convention of prefixing data members with d_ or m_ (“data” or “member”), we get: class Circle { double m_radius; i. publi const double& radius() const { return m_radius; + Moves Returning a reference is safe in this context, although this version of the code is (currently) equally good without the & . Use keyword auto wherever possible Supposing PI and radius were of different types (as we shall allow later), which one should we u: for the return value of a function which combines them? Just let the compiler figure it out, and if the surrounding codebase changes, the function will still be correct: const auto circumference() const { return 2. * PI * radius; + Declare functions which won't throw exceptions as noexcept Exception handling in C++ adds some overhead to the runtime calling conventions, so use_noexcept for more performant (and possibly more space-efficient) code: const auto area() const noexcept { return PI * radius * radius; + Use inline for small member functions declared but not defined within the class body All of our member functions are declared within the class body and so are implicitly inline (that is, the function body is reproduced at the call site). When declaring them (only), function definitions using inline are listed in the same header file, those which don’t are found in the implementation file. Be aware that the performance benefit for inlining decreases for longer functions, and the risk of unnecessary code-bloat increases. Consider making the class generic Maybe your users are fine using double the whole time, but one day one of them won't be (embedded systems can sometimes only use float , while supercomputers may offer arbitrary precision floating- point types). We'll be continuing this theme when we look at how to calculate PI for float and long double , here is just a basic outline: template class Circle { T m_radius; public inline static const auto PI = // ... I] wee const auto& radius() const noexcept { return m_radius; + const auto circumference() const noexcept { return T{2} * PI * m_radius; + & Set the class template type parameter in the constructor The type(s) of a generic object are needed at the call site, so Circle(2.5) would instantiate a Circle ,and Circle(4) would instantiatea Circle . This happens because of the template parameter being specified in the class constructor(s): template class Circle { T m_radius; //. public: Circle(T radius) { m_radius = radius; + I] we Use constructor member-initializers Rather than setting member variables in the body of the constructor, set them beforehand (in the same order they are declared in the class body) Circle(T radius) : m_radius{ radius } {} Note that the constructor function body can be (and often is) empty, but must be present. Use keyword explicit and move-semantics for constructors To avoid some implicit conversions use the explicit keyword before the constructor name. To avoid unnecessary copies when large objects (such as arbitrary-precision types) are passed to the constructor use move-semantics: explicit Circle(T&& radius) : m_radius{ std::move(radius) } {} Use constexpr if to return different types from a single function Inour Circle class, we may want to use the library function asin1()_ to provide a more accurate value with which to calculate PI when instantiating the template type parameter with long double . Conversely, we way want to use asinf() fortype float . The following code allows the unthinkable, returning different types (and accuracy of values) from a single function called make_pi() : template class Circle { T m_radius; static constexpr auto make_pi() { if constexpr(std::is_same_v) { return asinf(1.0F) * 2.@F; } else if constexpr(std::is_same_v) { return asinl(1.0L) * 2.0L; } else { return asin(T{1}) * T{2}5 } + public: inline static const auto PI = make_pi(); Wise ‘The header provides the template is_same_v<> , which when used as above produces a compile-time Boolean stating whether T isthesameas float or long double . The three return statements return a different type and value which is permissible because only one of them will be compiled into the object code due to use of if constexpr . (It is assumed that the fall-back to plain asin() uses a multi-precision variant in the case of such a type being used.) Our final version Making all(!) of the changes listed above results in the following, assuredly more modern, CH program: #include #include #include templatectypename T> class Circle { T mradius; static constexpr auto make_pi() { if constexpr(std::is_same_v) { return asinf(1.0F) * 2.@F5 y else if constexpr(std: s_same_v) { return asinl(1.@L) * 2.@L; else { return asin(T{1}) * T{2}; + } public: inline static const auto PI = make_pi(); explicit Circle(T&& radius) : m_radius{ std::move(radius) } {} const auto& radius() const noexcept { return m_radius; + const auto circumference() const noexcept { return T{2} * PI * m_radius; + const auto area() const noexcept { return PI * mradius * m_radius; + us int main() { Circle a(2.0); Circle b(10); Circle c(1.0f); Circle d(100.@L); -precision(20) ; << “a.circumference(): " << a.circumference() << '\n'; << “a.area(): "<< a.area() << ‘\n'5 << “b.circumference(): " << b.circumference() << ‘\n'; << “b.area(): " << bearea() << ‘\n"3 << “c.circumference(): " << ¢.circumference() << ‘\n"5 «<< “c.area(): " << c.area() << ‘\n'3 << "d.circumference(): " << d.circumference() << '\n'5 << "d.area(): " << dvarea() << "\n'3 This produces the following output when compiled with latest (trunk) gcc (MSVC doesn’t seem to support better precision from asinl() ): a.circumference(): 12.566370614359172464 a.area(): 12.566370614359172464 b.circumference(): 62.83185307179586232 b.area(): 314.15926535897932581 c.circumference(): 6.2831854820251464844 ¢.area(): 3.1415927410125732422 d.circumference(): 628.31853071795864768 d.area(): 31415.926535897932384 Just for the record, this code was tested with boost: :multiprecision: :cpp_dec_float_1¢@ and the values of PI calculated up to 30 figures matched online sources. In this article we've listed some traditional C++ styles, such as const-correctness, and more modern ones, such as use of noexcept and auto . Whether you're writing your own new code from scratch, or improving an existing codebase that someone else wrote, you should be well-equipped to put many of the Modern C++ jargon terms into practice. ante id ri 4 / Published by cpptutor View all posts by cpptutor Blog at WordPress.com.

You might also like