2.1-C++ - Best Practices for Systems.pdf
Document Details
Uploaded by BestTriumph
Tags
Full Transcript
C++ Best Practices for Systems Do it at compile time. Constructors / Destructors class MyClass { void test() { public: // Good // Constructor - MyClass my_class(10); // try to keep {} empty MyClass(int arg) : _arg(arg) {} // Avoid - extra copy MyClass my_class2 = MyClass(10); // Destruct...
C++ Best Practices for Systems Do it at compile time. Constructors / Destructors class MyClass { void test() { public: // Good // Constructor - MyClass my_class(10); // try to keep {} empty MyClass(int arg) : _arg(arg) {} // Avoid - extra copy MyClass my_class2 = MyClass(10); // Destructor ~MyClass() {} // Implicitly calls ~MyClass when // out of scope int _arg; }; } NRVO - Named Return Value Optimization std::vector<MyClass> get_many_classes () { std::vector<MyClass> result; for (int i = 0; i < 10; ++i) { result.push_back(MyClass(i)); } // Implicitly moves 'result', not a copy return result; } Smart Pointers - Usage #include <memory> std::shared_ptr<MyClass> pointer(nullptr); // make_shared<TypeName>(constructorArguments); pointer = std::make_shared<MyClass>(10); Smart Pointers - Usage // Check to see if pointer is not nullptr if (pointer) { // Dereferencing int value = pointer->_arg; int value2 = (*pointer)._arg; } else { // Pointer not valid } Smart Pointers - Shared Pointer // ref Count = 1 std::shared_ptr<MyClass> pointer(std::make_shared <MyClass>(10)); // refcount = 2 std::shared_ptr<MyClass> second_pointer = pointer; { // refcount = 3 std::shared_ptr<MyClass> third_pointer = pointer; // destructor - refcount = 2 } Smart Pointers - Unique Pointer std::unique_ptr<MyClass> pointer(std::make_unique <MyClass>(10)); // compiler error - operator= deleted std::unique_ptr<MyClass> second_pointer = pointer; // explicit move works fine std::unique_ptr<MyClass> third_pointer = std::move(pointer); Smart Pointers - Ownership // No ownership or memory management // Anyone with pointer can free, very dangerous int* pointer = new int; *pointer = 10; // Decentralized ownership - Free memory on 0 refcount // Slow for many pointers, encourages spaghetti code, hard to test. std::shared_ptr<int> my_shared = std::make_shared<int>(10); // Owned in current scope and frees at end current scope // 'my_unique' owns the memory std::unique_ptr<int> my_unique = std::make_unique<int>(10); Pointer Create() Helper Function static std::unique_ptr <MyClass> Create(int arg) { try { return std::make_unique <MyClass>(arg); } catch (const std::exception & e) { std::cout << "Error exception on MyClass::Create" << e.what() << std::endl; } catch (...) { std::cout << "Error unknown exception on MyClass::Create \n"; } return nullptr; } auto my_class = MyClass::Create(10); Enum Class enum class Type1 { subType1, subtype2 }; enum class Type2 { subType1, subtype2 }; Type1 type1 = Type1::subType1; Type2 type2 = Type2::subType1; // Fine with 'enum', doesn't compile with 'enum class' if (type1 == type2) {} constexpr constexpr int a = 5; constexpr int fibonnacci(int i) { if (i <= 2) return 1; return fibonnacci(i - 1) + fibonnacci(i - 2); } constexpr int fib_at_a = fibonnacci(a); Return Status enum class [[nodiscard]] Status { Ok, Error }; Status sanitizeName(std::string& name_in_out); // Compiles if (sanitizeName(name) == Status::Error) return 1; // Compiler error - Discarding output sanitizeName(name); Do it at compile time. Up Next: Templates. Templates std::vector<int> int_vector; std::vector<double> double_vector; std::sort(int_vector.begin(), int_vector.end()); std::sort(double_vector.begin(), double_vector.end()); Templates template <typename T> struct StoreValue { T data_; StoreValue(T data): data_(data) {} }; Template Specialization template <typename T> struct StoreValue { T data_; StoreValue(T data): data_(data) {} }; template <> struct StoreValue<double> { double data_; StoreValue(double data): data_(data) { std::cout << "This is a double!\n"; } } Template Metaprogramming template <int N> struct Fibonnaci { constexpr int value = Fibonnaci<N - 2>::value + Fibonnaci<N - 1>::value; }; template <> struct Fibonnaci<0> { constexpr int value = 1; }; template <> struct Fibonnaci<1> { constexpr int value = 1; }; A Tale of Three Patterns - Abstraction class Animal { public: virtual void noise() { std::cout << "Rawr!"; } }; class Dog: public Animal { public: void noise() override { std::cout << "Woof!"; } } … std::unique_ptr<Animal> animal = std::make_unique<Dog>(); animal->noise(); // known only at runtime A Tale of Three Patterns - Association class Dog { Animal animal_; public: void noise() { std::cout << "Woof!"; } void parent_noise() { animal_.noise(); } } … std::unique_ptr<Animal> animal = std::make_unique<Dog>(); // won't compile std::unique_ptr<Dog> dog = std::make_unique<Dog>(); // works / known at compiletime dog->noise(); A Tale of Three Patterns - Template Specialization enum class AnimalType { Generic, Dog }; template <AnimalType T = AnimalType::Generic> struct Animal { static void noise() { std::cout << "Rawr!\n"; } }; template <> struct Animal<AnimalType::Dog> { static void noise() { std::cout << "Woof!\n"; } }; Animal<>::noise(); // Rawr! known at compiletime (defaults to Generic) Animal<AnimalType::Generic>::noise(); // Rawr! known at compiletime Animal<AnimalType::Dog>::noise(); // Woof! Known at compiletime More on constexpr (see consteval, constinit in c++20…) constexpr int a = 5; constexpr int fibonnacci(int i) { if (i <= 2) return 1; return fibonnacci(i - 1) + fibonnacci(i - 2); } int fib_at_a = fibonnacci(a); // works at runtime too! static_assert template <class T> void swap(T& a, T& b) noexcept { static_assert(std::is_copy_constructible_v<T>, "Swap requires copying"); static_assert(std::is_nothrow_copy_constructible_v <T> && std::is_nothrow_copy_assignable_v <T>, "Swap requires nothrow copy/assign" ); auto c = b; b = a; a = c; } template <class T> struct data_structure { static_assert(std::is_default_constructible_v <T>, "Data structure requires default-constructible elements" ); }; “Compile Time” DevOps - Static Code Analysis ● ● ● ● ● Code Feature/Bug Compile Unit tests Integration Tests Lint “Compile Time” DevOps - Testing int add_numbers(int a, int b) { return a+b; } #include "gtest/gtest.h" TEST(AddNumbersTest, Operations ) { EXPECT_EQ(add_number(10, 10), 20)); EXPECT_EQ(add_number(10, -10), 0)); EXPECT_EQ(add_number(-10, 10), 0)); EXPECT_EQ(add_number(-10, -10), -20)); } “Compile Time” DevOps - Linting Tools ● Equivalent to squiggle lines in VSCode ○ Can add to compilation/submit suite “Compile Time” DevOps - Static Code Analysis (Borrow checker) { std::unique_ptr< int> a = make_unique <int>(); int& y = *a; y += 1; std::cout << "a: " << *a; // borrow checker throws error, y may go out of scope before x } “Compile Time” DevOps - Static Code Analysis (Borrow checker) { std::unique_ptr<int> a = make_unique<int>(); { // fix is to scope y ensuring it scope ends before ‘a’ int& y = *a; y += 1; } std::cout << "a: " << *a; } “Compile Time” DevOps - CMake ● Ties it all together ○ Can do all the above devops checks