LOADING

加载过慢请开启缓存 浏览器默认开启

Tab's Blog

Estas Tab-klavo ĉi tie

C++ Unleashed: Using xmake to Build Projects

Tutorial 2024/9/27

C++ Unleashed: From Zero to Hero

Previous chapter: Testing, Debugging, and Building

Go to Table of Contents

Using xmake to Build Projects

In this chapter, we will learn how to use xmake, a cross-platform build utility that simplifies the building and managing of C++ projects. With xmake, you can easily configure your build environment and manage dependencies without hassle.

阅读全文

C++ Unleashed: Error Handling and Exceptions

Tutorial 2024/9/27

C++ Unleashed: From Zero to Hero

Previous chapter: Standard Template Library (STL)

Go to Table of Contents

Error Handling and Exceptions

In C++, effective error handling is crucial for creating robust applications. This chapter will cover the concept of exceptions, how to use them for error handling, and best practices for managing errors in your code.

阅读全文

C++ Unleashed: Standard Template Library (STL)

Tutorial 2024/9/27

C++ Unleashed: From Zero to Hero

Previous chapter: Modern C++ Features

Go to Table of Contents

Standard Template Library (STL)

The Standard Template Library (STL) is a powerful feature of C++ that provides a collection of template classes and functions for managing data. It includes a wide range of data structures (containers), algorithms, and iterators that allow for efficient data manipulation and storage. In this chapter, we will explore the key components of the STL.

In this chapter, we will cover:

  • Containers
    • Sequence Containers
    • Associative Containers
    • Unordered Containers
    • Container Adaptors
  • Iterators
  • Algorithms
  • Function Objects (Functors)
阅读全文

C++ Unleashed: Modern C++ Features

Tutorial 2024/9/27

C++ Unleashed: From Zero to Hero

Previous chapter: Templates and Generic Programming

Go to Table of Contents

Chapter 9: Modern C++ Features

Modern C++ (C++11 and later) introduced a range of features that enhance the language’s expressiveness, performance, and safety. This chapter covers some of the most important features that you should know to write effective and modern C++ code today.

Table of Contents

  1. Type Inference with auto
  2. Range-Based For Loops
  3. Type Aliases (using)
  4. constexpr, consteval, and constinit
  5. decltype
  6. std::variant and std::optional
  7. nullptr

Type Inference with auto

Introduction

The auto keyword allows the compiler to automatically deduce the type of a variable at compile time. This feature simplifies code and improves readability, especially when dealing with complex types.

Usage

Basic Example:

#include <iostream>
#include <vector>

int main() {
    auto x = 42; // x is int
    auto y = 3.14; // y is double
    auto str = "Hello, World!"; // str is const char*

    std::vector<int> vec = {1, 2, 3, 4, 5};
    auto it = vec.begin(); // it is std::vector<int>::iterator

    std::cout << "x: " << x << ", y: " << y << ", str: " << str << std::endl;

    return 0;
}

Output:

x: 42, y: 3.14, str: Hello, World!

Best Practices

  • Use auto When Type is Obvious:

    auto count = 10; // Clearly an int
    
  • Avoid Obscuring Types:

    auto result = someFunction(); // If the return type of someFunction is unclear
    
  • Combine with decltype for Complex Deductions:

    decltype(auto) ref = someFunctionReturningReference();
    
  • Prefer Explicit Types for Function Return Types and Parameters:

    int add(int a, int b); // Clear return type
    

Range-Based For Loops

Introduction

Range-based for loops provide a more readable and concise way to iterate through containers, eliminating the need for explicit iterators or index-based loops. Introduced in C++11, they enhance code clarity and reduce the likelihood of errors associated with traditional loop constructs.

Syntax

for (auto& element : container) {
    // Use element
}

Example

#include <iostream>
#include <vector>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    for (const auto& num : numbers) {
        std::cout << num << " "; // Outputs: 1 2 3 4 5
    }
    std::cout << std::endl;

    return 0;
}

Output:

1 2 3 4 5

Best Practices

  • Use const auto& When Not Modifying Elements:

    for (const auto& num : numbers) {
        // Read-only access
    }
    
  • Use auto& When Modifying Elements:

    for (auto& num : numbers) {
        num *= 2; // Modify elements
    }
    
  • Avoid Unnecessary Copies:

    for (const auto& str : stringList) { /* ... */ } // Avoid copying strings
    
  • Use auto&& for Perfect Forwarding:

    for (auto&& element : container) { /* ... */ }
    

Type Aliases (using)

Introduction

Type aliases allow you to create alternative names for existing types, enhancing code readability and maintainability. The using keyword is preferred over typedef in modern C++ due to its clearer syntax and enhanced capabilities, especially with templates.

Syntax

using AliasName = ExistingType;

Example

#include <vector>
#include <string>

using StringList = std::vector<std::string>;

int main() {
    StringList names = {"Alice", "Bob", "Charlie"};
    return 0;
}

Best Practices

  • Simplify Complex Type Declarations:

    using IntPair = std::pair<int, int>;
    
  • Enhance Code Readability:

    using Byte = unsigned char;
    
  • Use Descriptive Alias Names:

    using Matrix = std::vector<std::vector<double>>;
    
  • Leverage with Templates:

    template <typename T>
    using Vec = std::vector<T>;
    
    Vec<int> intVec;
    

constexpr, consteval, and constinit

Modern C++ introduces several keywords that enhance compile-time programming, enabling more efficient and safer code. Understanding the distinctions and appropriate usage of constexpr, consteval, and constinit is crucial for leveraging these features effectively.

Understanding constexpr

constexpr specifies that the value of a variable or the return value of a function can be evaluated at compile time. It allows for optimizations and ensures that certain computations are performed during compilation rather than at runtime.

Usage:

#include <iostream>

constexpr int square(int x) {
    return x * x;
}

int main() {
    constexpr int result = square(5); // Computed at compile time
    int runtime_result = square(6);   // Also allowed, computed at runtime
    std::cout << "Square of 5 is: " << result << std::endl;
    std::cout << "Square of 6 is: " << runtime_result << std::endl;
    return 0;
}

Output:

Square of 5 is: 25
Square of 6 is: 36

Key Points:

  • Functions and Variables: Both can be declared constexpr.
  • Compile-Time Evaluation: When used in a context that requires a compile-time constant, the evaluation must occur at compile time.
  • Flexibility: constexpr functions can be used in both compile-time and runtime contexts.

Introducing consteval

consteval is a keyword introduced in C++20 that declares an immediate function. These functions are guaranteed to produce a compile-time constant and must be evaluated at compile time. Unlike constexpr, which allows both compile-time and runtime evaluations, consteval enforces compile-time evaluation.

Usage:

#include <iostream>

consteval int square(int x) {
    return x * x;
}

int main() {
    constexpr int sq = square(5); // Valid
    // int runtime_sq = square(5); // Error: requires compile-time evaluation
    std::cout << "Square of 5 is: " << sq << std::endl;
    return 0;
}

Output:

Square of 5 is: 25

Key Points:

  • Immediate Evaluation: Must be evaluated at compile time.
  • Type Safety: Ensures that functions producing constants are not mistakenly used in runtime contexts.
  • Use Cases: Ideal for functions that must generate constant expressions, enhancing both performance and reliability.

Exploring constinit

constinit is another keyword introduced in C++20 that ensures a variable is constantly initialized at compile time without making it immutable. Unlike constexpr, which makes a variable a compile-time constant, constinit only guarantees that the variable is initialized at compile time, allowing it to remain mutable during runtime.

Usage:

#include <iostream>

constinit int global_counter = 0;

int main() {
    global_counter += 5; // Allowed, since constinit doesn't make it immutable
    std::cout << "Global Counter: " << global_counter << std::endl;
    return 0;
}

Output:

Global Counter: 5

Key Points:

  • Static Initialization: Ensures that variables are initialized before any dynamic initialization occurs, preventing the static initialization order fiasco.
  • Mutable Variables: Unlike constexpr, variables declared with constinit can be modified after initialization.
  • Thread Safety: Guarantees that variables are initialized in a thread-safe manner at compile time.
  • Use Cases: Ideal for global and static variables that require early initialization but need to remain mutable during runtime.

Best Practices

  • Use constexpr: When you want flexibility for compile-time and runtime evaluations. Ideal for functions and variables that can benefit from both contexts.

  • Use consteval: When you need to guarantee that a function is evaluated at compile time, ensuring compile-time constants and preventing runtime evaluations.

  • Use constinit: For variables that must be initialized at compile time but need to remain mutable. Essential for global and static variables to ensure proper initialization order.


decltype

Introduction

The decltype keyword in C++ deduces the type of an expression at compile time. It is particularly useful in templates and when the type of an expression is complex or not easily determined. decltype enhances type safety and flexibility, allowing for more generic and adaptable code.

Usage

Basic Example:

#include <iostream>

int main() {
    int x = 5;
    decltype(x) y = 10; // y is of type int
    std::cout << "y: " << y << std::endl;
    return 0;
}

Output:

y: 10

With Complex Expressions:

#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    decltype(vec.begin()) it = vec.begin(); // it is std::vector<int>::iterator

    std::cout << "First element: " << *it << std::endl;
    return 0;
}

Output:

First element: 1

Best Practices

  • Use in Templates: Facilitate generic programming by deducing return types or variable types based on template parameters.

  • Combine with auto: Create concise type declarations without sacrificing clarity.

    auto x = someFunction();
    decltype(x) y = anotherFunction();
    
  • Avoid Overuse: While powerful, excessive use of decltype can make code harder to read. Use it judiciously to maintain clarity.


std::variant and std::optional

Introduction

C++17 introduced std::variant and std::optional to handle situations where a value might belong to multiple types or might be absent, respectively. These utilities enhance type safety and expressiveness in your code, reducing the reliance on traditional approaches like unions or sentinel values.

std::variant

Description:

std::variant is a type-safe union that can hold one value from a specified set of types. It provides a way to store and manage multiple types in a single variable, ensuring that only one type is active at any given time.

Usage:

#include <variant>
#include <iostream>
#include <string>

int main() {
    std::variant<int, std::string> data;
    
    data = 10;
    std::cout << std::get<int>(data) << std::endl; // Outputs: 10
    
    data = "Hello, Variant!";
    std::cout << std::get<std::string>(data) << std::endl; // Outputs: Hello, Variant!
    
    return 0;
}

Handling Multiple Types with std::visit:

#include <variant>
#include <iostream>
#include <string>

struct Visitor {
    void operator()(int i) const { std::cout << "int: " << i << std::endl; }
    void operator()(const std::string& str) const { std::cout << "string: " << str << std::endl; }
};

int main() {
    std::variant<int, std::string> data = "Hello";
    std::visit(Visitor{}, data); // Outputs: string: Hello
    
    data = 42;
    std::visit(Visitor{}, data); // Outputs: int: 42
    
    return 0;
}

Output:

string: Hello
int: 42

Best Practices

  • Use std::variant for Heterogeneous Data: When a variable needs to hold one of several types, std::variant provides a type-safe alternative to unions.

  • Handle All Possible Types: Use std::visit to ensure that all possible types are handled, enhancing type safety.

  • Avoid Excessive Nesting: Deeply nested std::variant instances can complicate code. Consider refactoring if necessary.

  • Leverage with std::monostate: Use std::monostate as the first type in a std::variant to represent an empty state.

std::optional

Description:

std::optional represents an object that may or may not contain a value, eliminating the need for sentinel values like nullptr or special error codes. It enhances code clarity by explicitly indicating the presence or absence of a value.

Usage:

#include <optional>
#include <iostream>

std::optional<int> findValue(bool shouldFind) {
    if (shouldFind) {
        return 42; // Optional value is present
    }
    return std::nullopt; // No value present
}

int main() {
    auto value = findValue(true);
    if (value) {
        std::cout << "Found value: " << *value << std::endl; // Dereference
    } else {
        std::cout << "No value found." << std::endl;
    }

    return 0;
}

Output:

Found value: 42

Simplified Access with operator* and operator->:

#include <optional>
#include <iostream>
#include <string>

std::optional<std::string> getName(bool valid) {
    if (valid)
        return "Alice";
    else
        return std::nullopt;
}

int main() {
    auto nameOpt = getName(true);
    if (nameOpt) {
        std::cout << "Name: " << *nameOpt << std::endl; // Outputs: Name: Alice
    }

    return 0;
}

Output:

Name: Alice

Best Practices

  • Use std::optional for Optional Return Values: Functions that might not always return a meaningful value can return std::optional instead of using sentinel values.

  • Avoid Using std::optional for Class Members: Prefer other design patterns, such as the Null Object pattern, to handle optional class members.

  • Prefer Checking has_value() or Using if (opt): Ensure that the presence of a value is checked before accessing it to prevent exceptions.

  • Leverage value_or for Default Values: Provide a fallback value when the optional is empty.

    int value = opt.value_or(0);
    

nullptr

Introduction

Introduced in C++11, nullptr is a type-safe pointer literal representing a null pointer. It replaces the traditional NULL macro and integer literal 0, providing better type safety and clarity in your code.

Usage

Basic Example:

#include <iostream>

void func(int* ptr) {
    if (ptr == nullptr) {
        std::cout << "Pointer is null." << std::endl;
    }
}

int main() {
    int* p = nullptr; // Safe null pointer
    func(p);

    return 0;
}

Output:

Pointer is null.

Function Overloading Ambiguity Resolution:

#include <iostream>

void func(int) {
    std::cout << "Function with int parameter." << std::endl;
}

void func(char*) {
    std::cout << "Function with char* parameter." << std::endl;
}

int main() {
    func(nullptr); // Calls func(char*) due to type safety
    return 0;
}

Output:

Function with char* parameter.

Advantages Over NULL and 0

  • Type Safety: nullptr has its own type (std::nullptr_t), preventing unintended conversions and function overloading ambiguities.

  • Clarity: Clearly indicates that the pointer is intended to be null, improving code readability.

  • Avoids Ambiguity: Eliminates confusion between integer 0 and null pointers in function overloading scenarios.

Best Practices

  • Use nullptr Instead of NULL or 0:

    int* ptr = nullptr;
    
  • Consistently Use nullptr Across Codebase: Ensure uniformity to leverage its type safety benefits fully.

  • Avoid Mixing nullptr with Other Null Representations: Stick to nullptr to maintain consistency and clarity.


Summary

In this chapter, you learned about important features introduced in modern C++:

  • Type Inference with auto: Automatically deducing variable types.
  • Range-Based For Loops: Simplifying iterations over containers.
  • Type Aliases (using): Creating alternative names for existing types.
  • constexpr, consteval, and constinit: Enhancing compile-time computations and initialization.
  • decltype: Deducing the type of expressions at compile time.
  • std::variant and std::optional: Handling multiple types and optional values safely.
  • nullptr: A type-safe null pointer constant.

These features make modern C++ more powerful, expressive, and safer, helping you write better code with less risk of errors. In the next chapter, we will delve into the Standard Template Library (STL), exploring its containers, algorithms, and iterators.

Next chapter: Standard Template Library (STL)

阅读全文

C++ Unleashed: Templates and Generic Programming

Tutorial 2024/9/27

C++ Unleashed: From Zero to Hero

Previous chapter: Object-Oriented Programming

Go to Table of Contents

Templates and Generic Programming

Templates and generic programming are powerful features in C++ that allow you to write flexible and reusable code. Templates enable functions and classes to operate with generic types, making your programs more abstract and adaptable.

In this chapter, we’ll cover:

  • Function Templates
  • Class Templates
  • Template Specialization
  • Variadic Templates
  • Concepts (C++20)
阅读全文

C++ Unleashed: Object-Oriented Programming

Tutorial 2024/9/27

C++ Unleashed: From Zero to Hero

Previous chapter: Pointers and References

Go to Table of Contents

Object-Oriented Programming

Object-Oriented Programming (OOP) is a cornerstone of C++ programming, enabling developers to model complex systems through classes and objects. This chapter delves into the essential principles of OOP, providing a solid foundation for building robust and maintainable applications.

阅读全文

C++ Unleashed: Pointers and References

Tutorial 2024/9/26

C++ Unleashed: From Zero to Hero

Previous chapter: Arrays and Strings

Go to Table of Contents

Pointers and References

Pointers and references are fundamental concepts in C++ that provide powerful ways to manipulate memory and data. They are essential for understanding how variables are stored and accessed, enabling efficient programming techniques such as dynamic memory allocation, passing large objects without copying, and implementing data structures like linked lists and trees.

In this chapter, we’ll cover:

  • Basics of Pointers
    • Pointer Declaration and Initialization
    • Dereferencing and Pointer Arithmetic
    • Null Pointers
  • References and Their Uses
    • Reference Declaration and Initialization
    • Differences Between Pointers and References
  • Dynamic Memory Allocation
    • new and delete
    • Arrays and Dynamic Allocation
  • Smart Pointers (unique_ptr, shared_ptr, weak_ptr)
    • Introduction to Smart Pointers
    • Using std::unique_ptr
    • Using std::shared_ptr and std::weak_ptr
  • Practical Examples
    • Swapping Variables Using Pointers and References
    • Implementing a Simple Dynamic Array
阅读全文

C++ Unleashed: Arrays and Strings

Tutorial 2024/9/26

C++ Unleashed: From Zero to Hero

Previous chapter: Functions

Go to Table of Contents

Arrays and Strings

Arrays and strings are fundamental data structures in C++ that allow you to store and manipulate collections of data. Understanding how to work with them is crucial for effective programming.

In this chapter, we’ll cover:

  • Arrays
    • C-Style Arrays
      • Declaration and Initialization
      • Accessing Elements
      • Multidimensional Arrays
      • Arrays and Functions
      • Limitations of C-Style Arrays
    • std::array
      • Introduction
      • Declaration and Initialization
      • Accessing Elements
      • Iterating Over std::array
      • Passing std::array to Functions
  • C-Style Strings
    • Character Arrays
    • Common Functions
  • The std::string Class
    • Basic Usage
    • String Operations
    • Converting Between Strings and Numbers
阅读全文

C++ Unleashed: Functions

Tutorial 2024/9/26

C++ Unleashed: From Zero to Hero

Previous chapter: Control Structures

Go to Table of Contents

Functions

Functions are fundamental building blocks in C++ programming that allow you to organize your code into reusable pieces. They help break down complex problems into smaller, manageable tasks, improve code readability, and facilitate code reuse.

In this chapter, we’ll cover:

  • Function Definition and Calls
  • Parameter Passing
  • Return Values
  • Default Arguments
  • Function Overloading
  • Recursive Functions
  • Lambda Expressions
阅读全文

C++ Unleashed: Control Structures

Tutorial 2024/9/26

C++ Unleashed: From Zero to Hero

Previous chapter: Getting Start

Go to Table of Contents

Control Structures

Control structures are fundamental to programming as they dictate the flow of execution within your programs. In C++, control structures allow you to make decisions, repeat actions, and alter the flow based on certain conditions. This chapter covers conditional statements, loops, and jump statements, including the use of goto.

阅读全文
1 ... 3 4
avatar
Tab Nahida

Estas Tab-klavo ĉi tie