C++ for Quantitative Finance: Hands-On - Object Oriented Programming
APLIED COMPUTATIO, QUANT METHODS
RELEVANT FOR QUANTITATIVE FINANCE
Don't get loss, what you have to understand here is what is a class (blueprint) and an object (instance of class). Then in practice (just code) the four pillars: abstraction, encapsulation, inheritance and polymorphism. Once understand it go to the best practice in modern C++, preferred composition respect to inheritance when is possible.
The content is organized as follows (click to be redirected):
- Setup and Configuration
- Language and Syntax (moderm C++, C++20)
- Debuging
- Memory Management
- Object-Oriented Programming (OOP) [YOUR ARE HERE]
- Efective and Functional Programming (Best Practice)
- Generic Programming (GP)
- High Performance Computation
- Applications in Computational Finance
- .
- .
- .
- .
- .
- Data Strcutures and Algorithms
- Reference
Each session have being public in a separate session due to the Blogger content volume restriction.
Go to the main publication (click here).
Object-Oriented Programming (OOP) is a programming paradigm that organizes code around objects, which combine data (attributes) and behavior (methods).
OOP models real-world entities as objects.
In OOP are two important concepts: class and object.
| Concept | Description |
|---|---|
| Class | Blueprint |
| Object | Instance of a class |
OOP have four pillars:
The core principles are Encapsulation, Abstraction, Inheritance, and Polymorphism, with encapsulation and composition being the most frequently used in modern C++ code.
- Abstraction: The essential details (expose); reduces complexity.
- Encapsulation: hide internal details and provide controlled access. Protect from unintended modification.
- Inheritance: create new classes from existing ones, code reuse.
- Polymorphism: multiple implementations of one interface for multiple behaviors. You can see a litle more details at the end of the blog.
In practice when we deal with inheritance we will have to use composition.
Moderm C++ prefer composition over inheritance. Essentially:
| Relationship | Use |
|---|---|
| "is-a" | Inheritance |
| "has-a" | Composition |
Composition creates looser coupling and is easier to maintain. You can change the engine implementation without redesigning the entire class hierarchy.
Use inheritance mainly when you truly need polymorphism (virtual functions and runtime behavior selection). For most code, composition leads to simpler, more flexible, and easier-to-test designs.
Virtual Functions
Virtual functions in C++ are member functions that allow runtime polymorphism.
“The base class defines the interface, but the derived class decides the behavior.”
They let you call the correct function depending on the actual object type, not just the pointer/reference type.
Use them when you want different classes to share a common interface but behave differently.
The main detail is that composition means building a class from other classes by making them member objects.
Principles ilustration
Modern C++ Best Practice
- Prefer composition over inheritance when possible.
- Use RAII for resource management.
-
Use
overridefor virtual functions. -
Keep data members
private. - Design small, focused classes.
Class are use to be save in .h files.
a practical example
Constructors and Destructors
Constructors initialize objects; destructors clean them up. Together they implement RAII, one of the most important principles in modern C++.
A constructor initializes an object when it is created.
A destructor (~) clean up resources (memory) when the object is destroyed.
Destructor (~): a function that is call w
Structue (struct)
A user-dfined type, similar to a class with public access as default.
Class and variables initialization (non-statics, statics and constans)
non-static belongs to each object, static belongs to the class, and const members must be initialized before the constructor body runs.
| Type | Initialization |
|---|---|
| Non-static | Constructor initializer list or default member initializer |
| Static | Declaration in class, definition outside (or inline static) |
| Const member | Constructor initializer list |
| Static const | Initialize in class |
constexpr | Initialize in class |
Modern C++ strongly prefers initialization at declaration and constructor initializer lists.
Non-Static Data Member initializers is a practical way to initialize variables into class to be reused. If you comprehend variables initialization and class use, your are done with this.
Constant can be modified.
Statics
This pointer
CONSTRUCTORS
For your learning path, understand deeply:
- Member initializer lists
- Delegating constructors
- Copy constructors
- Move constructors
-
explicitconstructors - Rule of Five
- RAII and resource ownership
| Constructor Type | Purpose |
|---|---|
| Default | Create empty/default object |
| Parameterized | Initialize with values |
| Default Arguments | Flexible initialization |
| Delegating | Reuse another constructor |
| Copy | Create from existing object |
| Move | Transfer ownership/resources |
| Explicit | Prevent implicit conversions |
| Deleted | Forbid construction pattern |
| Defaulted | Use compiler implementation |
Copy constructors
Move Semantics
As a researcher, especially in fields like computer science or machine learning, you often deal with large amounts of data and complex algorithms where performance is paramount. Understanding and implementing move semantics can be a game-changer for optimizing your code, making your simulations run faster, and allowing you to tackle more ambitious research problems. It's a powerful tool for efficient resource management in modern C++.
R-value and L-value
Understanding l-values and r-values is fundamental to mastering references, move semantics, and perfect forwarding.
L-value = an object you can refer to later.
R-value = a temporary value that is about to disappear. Modern C++ uses r-values to enable efficient move semantics and avoid unnecessary copies.
| Feature | L-value | R-value |
|---|---|---|
| Has a name | ✔️ | ❌ (usually) |
| Has a persistent memory location | ✔️ | ❌ (temporary) |
Can take its address (&) | ✔️ | Usually ❌ |
Binds to T& | ✔️ | ❌ |
Binds to T&& | ❌ | ✔️ |
| Can be moved from | Usually via std::move | ✔️ |
L-value can be use his addres, r-value can enable efficient transfer or resources.
std::move(s) converts the l-value s into an r-value reference, allowing its resources to be moved.
Move semantics rely on r-values.
std::vector<int> b = std::move(a); // Ownership of the memory is transferred instead of copied.
R-value references and move semantics were indeed introduced to enable the efficient transfer of resources, specifically by allowing temporary objects (R-values) to be moved rather than copied. This is a significant performance improvement, especially for objects that manage large resources like dynamic memory or file handles.
when you move an object, what typically happens is that the ownership of the underlying resources (like a pointer to dynamically allocated memory) is transferred from the source object to the destination object. The source object is then left in a valid, but unspecified, state, often with its resource pointers set to nullptr.
So, instead of a deep copy of the data, a move operation often just involves a few pointer reassignments. This is significantly faster and more memory-efficient.
Operator Overloading
he Challenge: Without operator overloading, adding two measurements like 5 meters + 3 seconds would either be a compile-time error or require cumbersome function calls like addMeasurements(measurement1, measurement2). This makes your code less readable and more prone to errors.
How Operator Overloading Helps:
- Creating a
MeasurementClass: You can define aMeasurementclass that stores both a numerical value and its unit (e.g.,double value; std::string unit;). - Overloading the
+Operator: You can then overload the+operator for yourMeasurementclass. This allows you to write intuitive code likeMeasurement totalDistance = distance1 + distance2;.- Concept Connection:
This directly relates to the "Overloading the plus operator for an
integer class" example from the lecture, but applied to a more complex
Measurementtype. - Relevance: In research, ensuring unit consistency is critical. Overloading the
+operator can include logic to check if units are compatible before addition (e.g., you can add meters to meters, but not meters to seconds). If units are incompatible, your overloaded operator could throw an error or perform a conversion if appropriate.
- Concept Connection:
This directly relates to the "Overloading the plus operator for an
integer class" example from the lecture, but applied to a more complex
- Overloading Comparison Operators (
<,>,==): Similarly, you can overload comparison operators to compare twoMeasurementobjects. For instance,if (measurement1 < measurement2)would work seamlessly.- Concept Connection: This ties into the "Overloading comparison operators" example, enabling natural comparisons between your custom data types.
- Relevance: When analyzing experimental data, you often need to compare different measurements to identify trends or outliers. Overloading these operators makes your analysis code much cleaner and easier to understand.
- Overloading the Insertion Operator (
<<): To easily display yourMeasurementobjects, you can overload the<<operator. This would let you print a measurement directly to the console likestd::cout << myMeasurement;which might output "10.5 meters".- Concept Connection: This is a direct application of the "Insertion Operator" concept from the third lecture, making your custom objects integrate smoothly with standard output streams.
- Relevance: For researchers, clear and concise output of data is essential for reporting and debugging.
By using operator overloading, you make your Measurement class behave much like built-in types, allowing for more natural and readable code when performing calculations and comparisons on scientific data. This "syntactic sugar" (as mentioned in the first lecture) significantly enhances the usability of your custom data structures, which is incredibly valuable in quantitative research and computer science applications.
| Operator | Member | Global | Return |
|---|---|---|---|
= | ✔ | ✘ | T& |
+=, -=, *=, /= | ✔ | ✘ | T& |
[] | ✔ | ✘ | T& / const T& |
() | ✔ | ✘ | Any |
++x, --x | ✔ | ✘ | T& |
x++, x-- | ✔ | ✘ | T |
+, -, *, / | Prefer global | ✔ | T |
==, != | Prefer global | ✔ | bool |
<<, >> | ✘ | ✔ | Stream reference |
Best Practices (Scott Meyers Style)
✔ Prefer member functions for operators that naturally modify or depend on the left-hand operand (=, [], (), +=, ++).
✔ Prefer non-member (often friend) functions for symmetric operators like <<, >>, +, and ==, especially when implicit conversions on the left operand are desirable.
✔ Pass operands as const T& unless copying or moving is required.
✔ Mark non-modifying operators as const.
✔ Return by reference (T&) for assignment-like operators (=, +=, ++ prefix).
✔ Return by value (T) for arithmetic operators (+, -) and postfix increment/decrement.
✔ Never overload an operator unless its behavior matches users' expectations.
friends
How this pointer come into play when we overload an operator?
This pointer is a hidden pointer, that automatically passed to every non-statis member functions.Every non-static member function receives an implicit this pointer that refers to the current object.
MEMORY MANAGEMENT
Smart pointers are particularly relevant for someone interested in computer science and machine learning, as efficient memory management is crucial in these fields.
Imagine you're developing a machine learning model, perhaps for image recognition, where you need to load and process large datasets. These datasets often require dynamic memory allocation.
The Challenge: In traditional C++, if you manually allocate memory for your image data (e.g., using
new), you're responsible for deallocating it (usingdelete) when you're done. For complex models with many data structures and functions, it's easy to forget todeletememory, leading to memory leaks. This can slow down your application or even crash it, especially when training models with massive amounts of data.The Smart Pointer Solution (
unique_ptrandshared_ptr):
Why it's relevant: In machine learning, you're
constantly dealing with large, dynamically allocated data structures
(tensors, matrices, image buffers). Using smart pointers like unique_ptr and shared_ptr
is a best practice that ensures your models run efficiently, prevent
memory-related bugs, and allow you to focus more on the algorithms
themselves rather than tedious memory management. It's a powerful tool
for building robust and scalable machine learning applications.
Use unique_ptr whenever an object has a single owner.
Examples:
- File objects
- Database connections
- Mathematical models
- Large vectors
- Tree nodes
This is the preferred smart pointer in modern C++.
Use shared_ptr when ownership is naturally shared.
Examples:
- GUI widgets
- Shared caches
- Shared configuration
- Graph structures
Use weak_ptr to avoid cyclic references.
Real-Life Example: Data Analysis in Scientific Research with static_cast
Imagine you're a researcher working on a project that involves analyzing large datasets, perhaps from an experiment measuring sensor readings over time. You've collected data where some values are stored as integers (e.g., number of events) and others as floating-point numbers (e.g., temperature readings). To perform accurate statistical analysis, you often need to ensure all your numerical data is treated consistently, especially when calculating averages or performing complex mathematical operations.
The Challenge: You have a list of integer sensor readings, but to calculate the average with precision, you need to treat them as floating-point numbers. If you simply divide two integers, C++ will perform integer division, truncating any decimal part, which would lead to inaccurate results for your research.
The Solution with
static_cast: This is wherestatic_castbecomes incredibly useful. You can explicitly convert your integer values to floating-point numbers before performing the division.int totalSensorReadings = 150; int numberOfMeasurements = 40; // Incorrect: Integer division will result in 3, not 3.75 double average_incorrect = totalSensorReadings / numberOfMeasurements; // average_incorrect will be 3.0 // Correct: Using static_cast to ensure floating-point division double average_correct = static_cast<double>(totalSensorReadings) / numberOfMeasurements; // average_correct will be 3.75- Why it's Relevant: In research, data integrity and precision are paramount. Using
static_castensures that your calculations are accurate, preventing potential misinterpretations of your experimental data. It's a safer way to perform explicit type conversions compared to C-style casts because it checks the validity of the cast at compile time, helping you catch potential errors early in your development process. This directly relates to the "Type Conversions - Part I (Basics)" lecture, specifically the concept ofstatic_castand its importance in preventing data loss and ensuring type safety.
This example highlights how a seemingly small detail like type conversion can have a significant impact on the reliability and accuracy of your research findings, making static_cast a valuable tool in your computer science toolkit!
