initial commit

This commit is contained in:
dl92
2026-01-06 15:05:27 +00:00
parent a94f174a39
commit 62ab80b1d6
25 changed files with 1291 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
{
"cmake.sourceDirectory": "/home/ys/code/cplusplus/GeminiTutorial/tutorial-2",
"cmake.configureArgs": [
"-DCMAKE_TOOLCHAIN_FILE=/home/ys/code/cplusplus/vcpkg/scripts/buildsystems/vcpkg.cmake"
]
}

View File

@@ -0,0 +1,31 @@
cmake_minimum_required(VERSION 3.15)
project(Tutorial2 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Find dependencies
find_package(spdlog CONFIG REQUIRED)
# Create a library for our application code
add_library(MyLib src/StockData_Old.cpp src/StockData_Modern.cpp src/CsvParser.cpp)
target_include_directories(MyLib PUBLIC src)
# Define our main executable
add_executable(App src/main.cpp)
# Link our App against spdlog and our library
target_link_libraries(App PRIVATE spdlog::spdlog MyLib)

View File

@@ -0,0 +1,73 @@
#include "CsvParser.h"
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <iostream>
#include <algorithm> // For std::remove_if
namespace CsvParser {
TickData parseTickCsvLine(std::string_view line) {
TickData tick;
std::string s_line(line); // Convert string_view to string for stringstream
std::stringstream ss(s_line);
std::string segment;
std::vector<std::string> seglist;
while(std::getline(ss, segment, ',')) {
seglist.push_back(segment);
}
if (seglist.size() != 3) {
throw std::runtime_error("CSV line does not have 3 segments: " + std::string(line));
}
// Parse timestamp (simplified for now, assumes epoch seconds)
// In a real scenario, this would involve more robust date/time parsing
try {
long long timestamp_sec = std::stoll(seglist[0]);
tick.timestamp = std::chrono::system_clock::from_time_t(timestamp_sec);
} catch (const std::exception& e) {
throw std::runtime_error("Failed to parse timestamp: " + seglist[0] + " - " + e.what());
}
// Parse price
try {
tick.price = std::stod(seglist[1]);
} catch (const std::exception& e) {
throw std::runtime_error("Failed to parse price: " + seglist[1] + " - " + e.what());
}
// Parse volume
try {
tick.volume = std::stol(seglist[2]);
} catch (const std::exception& e) {
throw std::runtime_error("Failed to parse volume: " + seglist[2] + " - " + e.what());
}
return tick;
}
std::vector<TickData> parseTickCsvFile(std::string_view filename)
{
std::vector<TickData> ticks;
std::ifstream file(std::string{filename}); // Convert string_view to string for ifstream
if (!file.is_open()) {
throw std::runtime_error("Could not open file: " + std::string(filename));
}
std::string line;
while (std::getline(file, line)) {
if (line.empty()) continue; // Skip empty lines
try {
ticks.push_back(parseTickCsvLine(line));
} catch (const std::runtime_error& e) {
std::cerr << "Error parsing line: \"" << line << "\" - " << e.what() << std::endl;
// Depending on requirements, could rethrow or continue
}
}
return ticks;
}
} // namespace CsvParser

View File

@@ -0,0 +1,11 @@
#pragma once
#include "TickData.h"
#include <string>
#include <vector>
#include <string_view> // For std::string_view
namespace CsvParser {
TickData parseTickCsvLine(std::string_view line);
std::vector<TickData> parseTickCsvFile(std::string_view filename);
} // namespace CsvParser

View File

@@ -0,0 +1,9 @@
#include "StockData_Modern.h"
// Constructor: Initialize the vector with a given size
StockData_Modern::StockData_Modern(size_t size) : data_(size) {}
// Public method to access data (with bounds checking provided by std::vector::at)
double& StockData_Modern::at(size_t index) {
return data_.at(index);
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include <cstddef> // For size_t
#include <vector>
class StockData_Modern {
public:
// Constructor
StockData_Modern(size_t size);
// No Copy Constructor, Copy Assignment, or Destructor needed!
// The compiler-generated versions work perfectly because `std::vector` handles itself.
// Public method to access data
double& at(size_t index);
private:
std::vector<double> data_;
};

View File

@@ -0,0 +1,37 @@
#include "StockData_Old.h"
#include <algorithm> // For std::copy
// Constructor: Allocate memory for the array
StockData_Old::StockData_Old(size_t size) : size_(size) {
data_ = new double[size_];
}
// Destructor: Free the allocated memory
StockData_Old::~StockData_Old() {
delete[] data_;
}
// Copy Constructor: Perform a deep copy
StockData_Old::StockData_Old(const StockData_Old& other) : size_(other.size_) {
data_ = new double[size_];
std::copy(other.data_, other.data_ + size_, data_);
}
// Copy Assignment Operator: Handle self-assignment and perform a deep copy
StockData_Old& StockData_Old::operator=(const StockData_Old& other) {
if (this != &other) { // Protect against self-assignment
// Free the old memory
delete[] data_;
// Allocate new memory and copy data
size_ = other.size_;
data_ = new double[size_];
std::copy(other.data_, other.data_ + size_, data_);
}
return *this;
}
// Public method to access data (no bounds checking for simplicity)
double& StockData_Old::at(size_t index) {
return data_[index];
}

View File

@@ -0,0 +1,25 @@
#pragma once // Use modern include guard
#include <cstddef> // For size_t
class StockData_Old {
public:
// Constructor
StockData_Old(size_t size);
// Copy Constructor (Rule 1 of 3)
StockData_Old(const StockData_Old& other);
// Copy Assignment Operator (Rule 2 of 3)
StockData_Old& operator=(const StockData_Old& other);
// Destructor (Rule 3 of 3)
~StockData_Old();
// Public method to access data
double& at(size_t index);
private:
double* data_;
size_t size_;
};

View File

@@ -0,0 +1,25 @@
#pragma once
#include <string>
#include <chrono>
#include <sstream> // For std::ostringstream
struct TickData {
std::chrono::system_clock::time_point timestamp;
double price;
long volume;
// Optional: for easy printing/debugging
std::string toString() const {
std::ostringstream oss;
// Convert timestamp to a readable format (simple example)
std::time_t time = std::chrono::system_clock::to_time_t(timestamp);
std::string time_str = std::ctime(&time);
time_str.pop_back(); // Remove trailing newline
oss << "Timestamp: " << time_str
<< ", Price: " << price
<< ", Volume: " << volume;
return oss.str();
}
};

View File

@@ -0,0 +1,26 @@
#pragma once
#include <string>
#include <iostream>
#include "spdlog/spdlog.h"
class Trade {
public:
Trade(const std::string& instrument, double price)
: instrument_(instrument), price_(price) {
spdlog::info("Trade constructor: {} @ {}", instrument_, price_);
}
~Trade() {
spdlog::info("Trade destructor: {} @ {}", instrument_, price_);
}
void print() const {
spdlog::info("Executing trade: {} for ${}", instrument_, price_);
}
private:
std::string instrument_;
double price_;
};

View File

@@ -0,0 +1,202 @@
#include "spdlog/spdlog.h"
#include "StockData_Old.h"
#include "StockData_Modern.h"
#include "Trade.h"
#include "TickData.h"
#include "CsvParser.h"
#include <memory> // For smart pointers
#include <iostream> // For std::cout, std::cerr
#include <vector> // For std::vector
#include <numeric> // For std::accumulate
#include <algorithm> // For std::max_element, std::for_each, std::count_if
#include <map> // For std::map (instead of unordered_map for simplicity in example)
#include <string_view> // For std::string_view
#include <fstream> // For std::ofstream
// --- Existing Tutorial 2.1 Demonstrations ---
void demonstrate_old_way() {
spdlog::info("--- Demonstrating the Old Way (Manual Memory Management) ---");
StockData_Old prices(5);
for (size_t i = 0; i < 5; ++i) {
prices.at(i) = 100.0 + i;
}
spdlog::info("Copied prices_copy1(prices)");
StockData_Old prices_copy1(prices); // Copy constructor
prices_copy1.at(0) = 999.0;
spdlog::info("prices.at(0) = {}", prices.at(0));
spdlog::info("prices_copy1.at(0) = {}", prices_copy1.at(0));
spdlog::info("Copied prices_copy2 = prices");
StockData_Old prices_copy2(1);
prices_copy2 = prices; // Copy assignment
prices_copy2.at(1) = 888.0;
spdlog::info("prices.at(1) = {}", prices.at(1));
spdlog::info("prices_copy2.at(1) = {}", prices_copy2.at(1));
spdlog::info("--- Leaving scope for Old Way demo ---");
}
void demonstrate_modern_way() {
spdlog::info("--- Demonstrating the Modern Way (RAII with std::vector) ---");
StockData_Modern prices(5);
for (size_t i = 0; i < 5; ++i) {
prices.at(i) = 100.0 + i;
}
spdlog::info("Copied prices_copy1(prices)");
StockData_Modern prices_copy1(prices); // Copy constructor (compiler-generated)
prices_copy1.at(0) = 999.0;
spdlog::info("prices.at(0) = {}", prices.at(0));
spdlog::info("prices_copy1.at(0) = {}", prices_copy1.at(0));
spdlog::info("Copied prices_copy2 = prices");
StockData_Modern prices_copy2(1);
prices_copy2 = prices; // Copy assignment (compiler-generated)
prices_copy2.at(1) = 888.0;
spdlog::info("prices.at(1) = {}", prices.at(1));
spdlog::info("prices_copy2.at(1) = {}", prices_copy2.at(1));
spdlog::info("--- Leaving scope for Modern Way demo ---");
}
void demonstrate_smart_pointers() {
spdlog::info("--- Demonstrating Smart Pointers ---");
// Demonstrate unique_ptr for exclusive ownership
{
spdlog::info("Creating a unique_ptr-managed Trade");
auto unique_trade = std::make_unique<Trade>("AAPL", 150.0);
unique_trade->print();
// unique_trade is automatically destroyed when this scope ends
} // ~Trade() is called here
// Demonstrate shared_ptr for shared ownership
{
spdlog::info("Creating a shared_ptr-managed Trade");
std::shared_ptr<Trade> shared_trade1;
{
auto shared_trade2 = std::make_shared<Trade>("GOOG", 2800.0);
shared_trade1 = shared_trade2; // Both pointers now share ownership
spdlog::info("shared_ptr use_count: {}", shared_trade1.use_count());
shared_trade2->print();
} // shared_trade2 goes out of scope, but the object is not destroyed
spdlog::info("shared_ptr use_count after inner scope: {}", shared_trade1.use_count());
shared_trade1->print();
// shared_trade1 is automatically destroyed when this scope ends
} // ~Trade() is called here
spdlog::info("--- Leaving scope for Smart Pointers demo ---");
}
// --- New Tutorial 2.2 Demonstrations ---
void generate_mock_csv_file(std::string_view filename, int num_lines) {
spdlog::info("Generating mock CSV file: {}", std::string(filename));
std::ofstream ofs(std::string{filename});
if (!ofs.is_open()) {
throw std::runtime_error("Could not create mock CSV file.");
}
for (int i = 0; i < num_lines; ++i) {
long long timestamp = std::chrono::system_clock::now().time_since_epoch().count() / 1000000000LL + i;
double price = 100.0 + (i * 0.5);
long volume = 100 + (i * 10);
ofs << timestamp << "," << price << "," << volume << "\n";
}
ofs.close();
}
void demonstrate_stl_containers_algorithms() {
spdlog::info("--- Demonstrating Modern STL Containers & Algorithms (Tutorial 2.2) ---");
const std::string_view csv_filename = "mock_tick_data.csv";
generate_mock_csv_file(csv_filename, 10);
std::vector<TickData> ticks;
try {
ticks = CsvParser::parseTickCsvFile(csv_filename);
spdlog::info("Successfully parsed {} ticks.", ticks.size());
} catch (const std::runtime_error& e) {
spdlog::error("Error parsing CSV file: {}", e.what());
return;
}
// Example 1: Find the highest price
auto max_price_it = std::max_element(ticks.begin(), ticks.end(),
[](const TickData& a, const TickData& b) {
return a.price < b.price;
});
if (max_price_it != ticks.end()) {
spdlog::info("Highest price found: {}", max_price_it->price);
}
// Example 2: Calculate total volume
long total_volume = std::accumulate(ticks.begin(), ticks.end(), 0LL,
[](long sum, const TickData& tick) {
return sum + tick.volume;
});
spdlog::info("Total volume: {}", total_volume);
// Example 3: Filter ticks above a certain price (e.g., > 102.0)
std::vector<TickData> high_price_ticks;
std::copy_if(ticks.begin(), ticks.end(), std::back_inserter(high_price_ticks),
[](const TickData& tick) {
return tick.price > 102.0;
});
spdlog::info("Number of ticks with price > 102.0: {}", high_price_ticks.size());
// std::for_each(high_price_ticks.begin(), high_price_ticks.end(),
// [](const TickData& tick) { spdlog::info(" {}", tick.toString()); });
// Example 4: Average price using a lambda
double sum_prices = 0.0;
std::for_each(ticks.begin(), ticks.end(),
[&sum_prices](const TickData& tick) {
sum_prices += tick.price;
});
if (!ticks.empty()) {
spdlog::info("Average price: {}", sum_prices / ticks.size());
}
// Example 5: Aggregate volume by second (using std::map for simplicity)
// For high performance, a custom hash map or a more direct time-series approach would be used.
std::map<long long, long> volume_by_second;
for (const auto& tick : ticks) {
long long sec = std::chrono::duration_cast<std::chrono::seconds>(tick.timestamp.time_since_epoch()).count();
volume_by_second[sec] += tick.volume;
}
spdlog::info("Volume by second (first 3 entries):");
int count = 0;
for (const auto& entry : volume_by_second) {
if (count >= 3) break;
spdlog::info(" Timestamp (sec): {}, Volume: {}", entry.first, entry.second);
count++;
}
spdlog::info("--- Leaving scope for STL Containers & Algorithms demo ---");
}
int main() {
spdlog::set_level(spdlog::level::info);
spdlog::info("Starting Tutorial 2: Core Language, Libraries, and Concepts");
demonstrate_old_way();
std::cout << "\n";
demonstrate_modern_way();
std::cout << "\n";
demonstrate_smart_pointers();
std::cout << "\n";
demonstrate_stl_containers_algorithms();
spdlog::info("Tutorial 2 finished.");
return 0;
}

View File

@@ -0,0 +1,109 @@
# Tutorial 2.1 Plan: Resource Management & The Rule of Zero
**Objective:** To master modern C++ resource management techniques, moving away from manual memory handling (`new`/`delete`) to the robust RAII (Resource Acquisition Is Initialization) idiom. This tutorial will demonstrate the "Rule of Zero" by refactoring a C++98-style class that requires manual memory management into a modern, safer equivalent using standard library containers and smart pointers.
---
## 1. Project Setup
To keep our tutorials organized, we will create a new directory for this lesson.
1. **Create a `tutorial-2` directory:** Inside the `GeminiTutorial` project root.
2. **Copy Configuration:** Copy the `CMakeLists.txt` and the `.vscode` directory from `tutorial-1` into the new `tutorial-2` directory. This preserves our build and debug configurations.
3. **Update `CMakeLists.txt`:** Modify the `project()` name at the top of `tutorial-2/CMakeLists.txt` from `Tutorial1` to `Tutorial2`.
---
## 2. The "Old Way": Manual Memory Management (The Rule of Three/Five)
First, we'll create a class that manually manages a dynamic array of doubles. This will highlight the complexity and risks of the C++98 approach.
**File:** `tutorial-2/src/StockData_Old.h`
```cpp
#pragma once // Use modern include guard
#include <cstddef> // For size_t
class StockData_Old {
public:
// Constructor
StockData_Old(size_t size);
// Copy Constructor (Rule 1 of 3)
StockData_Old(const StockData_Old& other);
// Copy Assignment Operator (Rule 2 of 3)
StockData_Old& operator=(const StockData_Old& other);
// Destructor (Rule 3 of 3)
~StockData_Old();
// Public method to access data
double& at(size_t index);
private:
double* data_;
size_t size_;
};
```
We will also create the corresponding `.cpp` file to implement these special member functions, demonstrating the need for deep copies and manual cleanup.
---
## 3. The "Modern Way": Automatic Resource Management (The Rule of Zero)
Next, we will create a modern equivalent that leverages `std::vector`. This class will achieve the same functionality with significantly less code and greater safety.
**File:** `tutorial-2/src/StockData_Modern.h`
```cpp
#pragma once
#include <vector>
class StockData_Modern {
public:
// Constructor
StockData_Modern(size_t size);
// No Copy Constructor, Copy Assignment, or Destructor needed!
// The compiler-generated versions work perfectly because `std::vector` handles itself.
// Public method to access data
double& at(size_t index);
private:
std::vector<double> data_;
};
```
This class adheres to the **Rule of Zero**, as it owns its resources through a standard library container and requires no custom resource management code.
---
## 4. Introducing Smart Pointers for Single Object Ownership
While containers are for sequences of objects, smart pointers manage the lifetime of a single dynamically allocated object.
1. **`std::unique_ptr`:** Represents exclusive ownership. The object is destroyed automatically when the `unique_ptr` goes out of scope.
2. **`std::shared_ptr`:** Represents shared ownership. The object is destroyed only when the last `shared_ptr` pointing to it is destroyed.
We will create a simple `Trade` class and a `main.cpp` to demonstrate creating and managing `Trade` objects on the heap using both `std::unique_ptr` and `std::shared_ptr`.
---
## 5. Updating the Build System and Main Application
We will modify `tutorial-2/CMakeLists.txt` to include all our new source files (`.cpp` files for `StockData_Old`, `StockData_Modern`, and the new `main.cpp`).
The `main.cpp` will first use `StockData_Old` to illustrate its usage (and potential pitfalls), then use `StockData_Modern` to show the simplicity and safety of the modern approach, and finally demonstrate the usage of smart pointers.
---
## 6. Summary and Key Takeaways
This tutorial will provide hands-on experience with the core principles of modern C++ resource management:
* **RAII:** Let objects manage resources. Acquisition is in the constructor, release is in the destructor.
* **Rule of Zero:** By using standard library classes like `std::vector`, `std::string`, `std::unique_ptr`, and `std::shared_ptr` to handle resource ownership, your own classes often don't need any custom destructor, copy/move constructors, or assignment operators.
* **Expressing Ownership:** Choose the right tool for the job:
* `std::vector` for owning a dynamic array of elements.
* `std::unique_ptr` for exclusive ownership of a single heap-allocated object.
* `std::shared_ptr` for shared ownership of a single heap-allocated object.

View File

@@ -0,0 +1,10 @@
Take Aways:
1. std::string_view: manipulation of strings without the costs of ownership:
i. copying and storing large string objects
implemented as an type storing the first element and lenght of string
see [link](https://stackoverflow.com/questions/20803826/what-is-string-view)
2. Explain sync pattern or idiom in relation to string_view
3. clang-tidy static checking -how does it work?

View File

@@ -0,0 +1,83 @@
# Tutorial 2.2 Plan: Modern STL Containers & Algorithms
**Objective:** Use the workhorse containers and algorithms with a focus on performance. The project involves parsing mock CSV tick data using algorithms and lambdas to perform queries.
---
## 1. Project Setup
We will continue working within the `tutorial-2` directory. No new directory creation is needed.
## 2. Defining Tick Data Structure
We'll define a simple struct to represent a tick, containing a timestamp, price, and volume.
**File:** `tutorial-2/src/TickData.h`
```cpp
#pragma once
#include <string>
#include <chrono>
struct TickData {
std::chrono::system_clock::time_point timestamp;
double price;
long volume;
// Optional: for easy printing/debugging
std::string toString() const;
};
```
## 3. CSV Parsing Utility
We'll implement a function to parse a single line of CSV data into a `TickData` object. This will involve string manipulation and conversion. `std::string_view` will be crucial here for zero-copy parsing.
**File:** `tutorial-2/src/CsvParser.h`
```cpp
#pragma once
#include "TickData.h"
#include <string>
#include <vector>
namespace CsvParser {
TickData parseTickCsvLine(std::string_view line);
std::vector<TickData> parseTickCsvFile(std::string_view filename);
} // namespace CsvParser
```
**File:** `tutorial-2/src/CsvParser.cpp`
```cpp
#include "CsvParser.h"
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <iostream> // For error reporting
// Implement parseTickCsvLine and parseTickCsvFile
```
## 4. Main Application: Parsing and Querying
In `tutorial-2/src/main.cpp`, we will:
1. Generate some mock CSV tick data (or read from a static file).
2. Use `CsvParser::parseTickCsvFile` to load the data into a `std::vector<TickData>`.
3. Perform various queries using modern STL algorithms and lambdas:
* Find the highest price.
* Calculate the total volume.
* Filter ticks above a certain price.
* Find the average price within a time range.
* Use `std::unordered_map` to aggregate volume by some key (e.g., minute of the hour).
## 5. Updating the Build System
Modify `tutorial-2/CMakeLists.txt` to include `TickData.cpp` and `CsvParser.cpp` (if needed, `TickData` might just be a header). Ensure proper linking.
## 6. Summary and Key Takeaways
This tutorial will provide hands-on experience with:
* Efficient string handling using `std::string_view`.
* Storing structured data in `std::vector`.
* Performing data transformations and queries using STL algorithms and lambdas.
* Using `std::unordered_map` for fast lookups and aggregations.