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):

  1. Setup and Configuration
  2. Language and Syntax (moderm C++, C++20)
  3. Debuging 
  4. Memory Management 
  5. Object-Oriented Programming (OOP) [YOUR ARE HERE]
  6. Efective and Functional Programming (Best Practice)
  7. Generic Programming (GP)
  8. High Performance Computation 
  9. Applications in Computational Finance
    1. .
    2. .
    3. .
    4. .
  10. Data Strcutures and Algorithms
  11. 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.

ConceptDescription
ClassBlueprint
ObjectInstance of a class

 

// Object Oriented Programming
 
// class Car {} -> class 
// car1, car2 -> objects

class Car {
public:
    std::string brand;
   
    void Start() {
        std::cout << "Engine started\n";
    }
};

int main() {
    Car car;
    car.brand = "Toyota";
    car.Start();
}

 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.

  1. Abstraction: The essential details (expose); reduces complexity.
  2. Encapsulation: hide internal details and provide controlled access. Protect from unintended modification.
  3. Inheritance: create new classes from existing ones, code reuse.
  4. Polymorphism: multiple implementations of one interface for multiple behaviors. You can see a litle more details at the end of the blog.


/* ////////////////////////////////////////////////// */
/*  PRINCIPLES OF OOP */
// nota: we go to see this car illustrative example in 
// a full implementation in the next seccions.. 
/* ////////////////////////////////////////////////// */
 
/* Abstraction */

class printer {
    public:
        void print() {
            //
        }
};
 
class Vehicle {
public:
    virtual void start() = 0;
    virtual void stop() = 0;
    virtual void move() const = 0;
};

/* --------------------------------------------------- */
/* Encapsulation*/
 
class Car {
private:
    double fuelLevel;
    double speed;
};
 
int main() {
    // outside can not directly do this:
    /*
     car.speed = -100;
     car.fuelLevel = -50;
    */
    // instead you can use controlled methods:
    car.addFuel(50);
    car.accelerate(60);
    car.brake(20);
}

class BankAccount {
    private:
        double balance; // this is the encapsulation

    public:
        void Deposit(double amount) {
            balance += amount;
        }

        double GetBalance() const{
            return balance;
        }
};

/* --------------------------------------------------- */
/* Inheritance : - Is a -*/
 
class Car : public Vehicle {
 
};
 
class ElectricCar : public Vehicle {
 
};
 
#include <iostream>

class Animal {
    public:
        void Eat() {
            std::cout << "Eating\n";
        }

        virtual void Speak() {
            std::cout << "Animal sound\n";
        }
};

class Dog: public Animal {
    public:
        void Bark() {
            std::cout << "Woof!\n";
        }

};

Dog dog;
dog.Eat();
dog.Speak();
dog.Bark();
 
/* --------------------------------------------------- */
/* Composition: - Has a -*/

class Car {
    private:
        Engine engine;
};

/* --------------------------------------------------- */
/* Polymorphis */
 
void testVehicle(Vehicle& vehicle) {
    vehicle.start();
    vehicle.move();
    vehicle.stop();
}

int main() {
    ...
    for (const auto& vehicle : garage) {
        testVehicle(*vehicle);
    }

    return 0;
}
 
 
class Dog: public Animal {
    public:
        void Speak() override {
            std::cout << "Woof!\n";
        }
};

class Cat: public Animal {
    public:
        void Speak() override {
            std::cout << "Meow!\n";
        }
};

Animal *a = new Dog();
a->Speak(); //woof:

/* --------------------------------------------------- */
/* --------------------------------------------------- */

 

 

In practice when we deal with inheritance we will have to use composition.

Moderm C++ prefer composition over inheritance. Essentially:

RelationshipUse
"is-a"Inheritance
"has-a"Composition

 

// Composition vs Inheritance

// Inheritance ("is-a")
class Dog : public Animal {
};
 
// Composition ("has-a")
class Car {
    Engine engine; // A Car has an Engine.
};
 

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.

 

/* composition*/

 class Engine {
        public:
            void Start() {
                std::cout << "Engine started\n";
            }
 };

 class Car {
    private:
        Engine engine; //car has an engine [Composition]

    public:
        void Start() {
            engine.Start();
        }
 };

 Car car;
 car.Start();

 

Principles ilustration

 

#include <iostream>
#include <memory>
#include <string>
#include <vector>

/* ============================================================
   COMPOSITION
   Engine is a separate class.
   A Car HAS an Engine.
   ============================================================ */

class Engine {
private:
    int horsepower;
    bool running;

public:
    Engine(int hp)
        : horsepower(hp), running(false)
    {}

    void start() {
        running = true;
        std::cout << "Engine started.\n";
    }

    void stop() {
        running = false;
        std::cout << "Engine stopped.\n";
    }

    bool isRunning() const {
        return running;
    }

    int getHorsepower() const {
        return horsepower;
    }
};


/* ============================================================
   ABSTRACTION
   Vehicle is an abstract base class.
   It defines WHAT every vehicle can do, but not HOW.
   ============================================================ */

class Vehicle {
private:
    std::string brand;
    int year;

public:
    Vehicle(const std::string& brand, int year)
        : brand(brand), year(year)
    {}

    virtual ~Vehicle() = default;

    std::string getBrand() const {
        return brand;
    }

    int getYear() const {
        return year;
    }
    virtual void start() = 0;
    virtual void stop() = 0;
    virtual void move() const = 0;
};


/* ============================================================
   INHERITANCE + ENCAPSULATION + COMPOSITION
   Car IS A Vehicle.
   Car HAS AN Engine.
   Internal data is private and controlled through methods.
   ============================================================ */

class Car : public Vehicle {
private:
    Engine engine;
    double fuelLevel;
    double speed;

public:
    Car(const std::string& brand, int year, int horsepower)
        : Vehicle(brand, year),
          engine(horsepower),
          fuelLevel(0.0),
          speed(0.0)
    {}

    void addFuel(double amount) {
        if (amount > 0) {
            fuelLevel += amount;
        }
    }

    double getFuelLevel() const {
        return fuelLevel;
    }

    double getSpeed() const {
        return speed;
    }

    void accelerate(double amount) {
        if (!engine.isRunning()) {
            std::cout << "Cannot accelerate. Engine is off.\n";
            return;
        }

        if (fuelLevel <= 0) {
            std::cout << "Cannot accelerate. No fuel.\n";
            return;
        }

        if (amount > 0) {
            speed += amount;
            fuelLevel -= amount * 0.05;

            if (fuelLevel < 0) {
                fuelLevel = 0;
            }
        }
    }

    void brake(double amount) {
        if (amount > 0) {
            speed -= amount;

            if (speed < 0) {
                speed = 0;
            }
        }
    }

    void start() override {
        engine.start();
    }

    void stop() override {
        speed = 0;
        engine.stop();
    }

    void move() const override {
        std::cout << getBrand()
                  << " car is moving at "
                  << speed
                  << " km/h with engine power "
                  << engine.getHorsepower()
                  << " HP.\n";
    }
};


/* ============================================================
   INHERITANCE + POLYMORPHISM
   ElectricCar is also a Vehicle.
   It implements the same abstract interface differently.
   ============================================================ */

class ElectricCar : public Vehicle {
private:
    double batteryLevel;
    double speed;
    bool poweredOn;

public:
    ElectricCar(const std::string& brand, int year)
        : Vehicle(brand, year),
          batteryLevel(100.0),
          speed(0.0),
          poweredOn(false)
    {}

    double getBatteryLevel() const {
        return batteryLevel;
    }

    void charge(double amount) {
        if (amount > 0) {
            batteryLevel += amount;

            if (batteryLevel > 100) {
                batteryLevel = 100;
            }
        }
    }

    void accelerate(double amount) {
        if (!poweredOn) {
            std::cout << "Cannot accelerate. Electric car is off.\n";
            return;
        }

        if (batteryLevel <= 0) {
            std::cout << "Cannot accelerate. Battery empty.\n";
            return;
        }

        if (amount > 0) {
            speed += amount;
            batteryLevel -= amount * 0.03;

            if (batteryLevel < 0) {
                batteryLevel = 0;
            }
        }
    }

    void start() override {
        poweredOn = true;
        std::cout << "Electric system powered on.\n";
    }

    void stop() override {
        speed = 0;
        poweredOn = false;
        std::cout << "Electric system powered off.\n";
    }

    void move() const override {
        std::cout << getBrand()
                  << " electric car is moving at "
                  << speed
                  << " km/h with battery level "
                  << batteryLevel
                  << "%.\n";
    }
};


/* ============================================================
   POLYMORPHISM
   This function works with ANY Vehicle.
   It does not need to know whether the object is a Car,
   ElectricCar, Truck, Motorcycle, etc.
   ============================================================ */

void testVehicle(Vehicle& vehicle) {
    vehicle.start();
    vehicle.move();
    vehicle.stop();
    std::cout << "----------------------\n";
}


/* ============================================================
   MAIN PROGRAM
   ============================================================ */

int main() {
    Car toyota("Toyota", 2022, 180);
    toyota.addFuel(50);
    toyota.start();
    toyota.accelerate(60);
    toyota.move();
    toyota.brake(20);
    toyota.move();
    toyota.stop();

    std::cout << "======================\n";

    ElectricCar tesla("Tesla", 2024);
    tesla.start();
    tesla.accelerate(80);
    tesla.move();
    tesla.stop();

    std::cout << "======================\n";

    std::vector<std::unique_ptr<Vehicle>> garage;

    garage.push_back(std::make_unique<Car>("Honda", 2021, 150));
    garage.push_back(std::make_unique<ElectricCar>("Nissan Leaf", 2023));

    for (const auto& vehicle : garage) {
        testVehicle(*vehicle);
    }

    return 0;
}

Modern C++ Best Practice

  • Prefer composition over inheritance when possible.
  • Use RAII for resource management.
  • Use override for 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

 

#include <iostream>
#include <string>

class Config {
    private:
        std::string name;
    public:
        Config() {
            std::cout << "Constructor called\n";
        }

        ~Config() {
            std::cout << "Destruction calles\n";
        }

        Congig2(std::string n)
            : name(std::move(n))
            {

            }

        ~Config2() = default;


};

Config cfg;

int main() {
    Config cfg;
}

 

 

Structue (struct)

A user-dfined type, similar to a class with public access as default. 

 

 

#include <iostream>

struct Point {
    int x;
    int y;
}

/*
void Drawline(int x1, int y1, int x2, int y2) {

}
*/

void Drawline(Point start, Point end) {
    std::cout << start.x << std::endl;
}

int main() {
    return 0;
}


 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.

 

TypeInitialization
Non-staticConstructor initializer list or default member initializer
StaticDeclaration in class, definition outside (or inline static)
Const memberConstructor initializer list
Static constInitialize in class
constexprInitialize in class

 

#include <iostream>
#include <string>

// in line with best practice in C++

class Config {
    private:
        // 1) Non-static variable
        // Each object has its own copy.
        std::string name;

        // 2) Non-static with default initialization
        // Used if constructor does not override it.
        int level = 1;

        // 3) Const variable
        // Must be initialized in the constructor initializer list.
        const int id;

    public:
        // 4) Static variable
        // Shared by all objects of the class.
        inline static int object_count = 0; // C++17+

        // 5) Static constexpr constant
        // Shared compile-time constant.
        static constexpr double version = 1.0;

        // Constructor with a member initializer list
        // Schematic:
        // id(i)      -> initializes const member
        // name(n)    -> initializes non-static member
        // level(lvl) -> overrides default value
        Config(int i, std::string n, int lvl) //parameters
            : id(i), name(n), level(lvl) //member initializer list
        {
            ++object_count;
        }

        void Print() const {
            std::cout << "id: " << id << '\n';
            std::cout << "name: " << name << '\n';
            std::cout << "level: " << level << '\n';
            std::cout << "version: " << version << '\n';
            std::cout << "object_count: " << object_count << '\n';
        }
};

int main() {
    Config a(1, "Main config", 5);
    Config b(2, "Backup config", 3);

    a.Print();
    b.Print();

    return 0;
}

 

 

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:

  1. Member initializer lists
  2. Delegating constructors
  3. Copy constructors
  4. Move constructors
  5. explicit constructors
  6. Rule of Five
  7. RAII and resource ownership
  8.  
  9.  
  10.  

 

Constructor TypePurpose
DefaultCreate empty/default object
ParameterizedInitialize with values
Default ArgumentsFlexible initialization
DelegatingReuse another constructor
CopyCreate from existing object
MoveTransfer ownership/resources
ExplicitPrevent implicit conversions
DeletedForbid construction pattern
DefaultedUse compiler implementation

 

 

#include <iostream>
#include <string>
#include <utility>

/* CONSTRUCTORS */

//Constructors initializes an object when it is created.

class Config {
        public:
            Config() {
                std::cout << "Created\n";
            }
};

//Config cfg;

// ------------------------------------------- *
// Default constructor

class Config2 {
    public:
        Config2() = default;
};

//Config2 cfg2;

// ------------------------------------------- *
// Parametrized constructor

class Config3 {
    private:
        int id;
    public:
        Config3(int i)
            : id(i)
        {}
};

//Config3 cfg3(45);


// ------------------------------------------- *
// with default argument
class Config4 {
    private:
        int id;
    public:
        Config4(int i = 0)
            : id(i)
        {}
};

//Config4 a;
//Config4 b(43);

// ------------------------------------------- *
// Delegating: call another constructor of the same class

class Config5 {
    private:
        int id;
        std::string name;

    public:
        Config5(int i, std::string n)
            : id(i), name(n)
        {}

        Config5()
            : Config5(0, "Default")
        {}
};

//Flow
/*
Config()
    ↓
Config(0, "Default")
*/

// ------------------------------------------- *
// Copy: create an object from an other object

class Config6 {
    private:
        int id;

    public:
        Config6(int i)
            : id(i)
        {}

        Config6(const Config6& other)
            : id(other.id)
        {}
};

//Config6 a(1);
//Config6 b = a;

// ------------------------------------------- *
// Move: transfer resource from an other object

class Config7 {
    public:
        std::string name;

        Config7(std::string n)
            : name(std::move(n))
        {}

        Config7(Config7&& other) noexcept
            : name(std::move(other.name))
        {}
};

//Config7 a("Large Data");
//Config7 b = std::move(a);

// ------------------------------------------- *
// Explicit: prevent unintended implicit conversions.

class Config8 {
    private:
        int id;

    public:
        explicit Config8(int id)
            : id(id)
        {}
};

//Config8 cfg8(42);
// Config cfg = 42 // error

// ------------------------------------------- *
// delecte: prevent object creation in certain ways.
// Forbit construction pattern.
class Config9 {
    private:
        int id;

    public:
        Config9() = delete;

        Config9(int id)
            : id(id)
        {}
};

//Config9 (42);
//Config b; //error

// ------------------------------------------- *
// Compiler-Generated: ask the compiler to generate then.

class Config10 {
    public:
        Config10() = default;
        Config10(const Config10&) = default;
        Config10(Config10&&) = default;
};


int main() {
    Config cfg;
    Config2 cfg2;
    Config3 cfg3(45);

    Config4 cfg4a;
    Config4 cfg4b(43);

    Config5 cfg5;

    Config6 cfg6_a(1);
    Config6 cfg6_b = cfg6_a;

    Config7 cfg7a("Large Data");
    Config7 cfg7b = std::move(cfg7a);

    Config8 cfg8(42);

    Config9 cfg9(42);

    Config10 cfg10;

    return 0;
}

 

 

 

 

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.

FeatureL-valueR-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 fromUsually 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.

 

//constructor and move semantic illustration

#include <iostream>


class MyVector {
    private:
        int* data;
        std::size_t size;

    public:
        MyVector(std::size_t n)
            : data(new int[n]), size(n)
        {
            std::cout << "Constructor\n";
        }

        ~MyVector() {
            delete[] data;
            std::cout << "Destructor\n";
        }

        // Disable copying
        MyVector(const MyVector&) = delete;
        MyVector& operator=(const MyVector&) = delete;

        // Move constructor
        MyVector(MyVector&& other) noexcept
            : data(other.data), size(other.size)
        {
            std::cout << "Move Constructor\n";

            other.data = nullptr;
            other.size = 0;
        }

 };


 MyVector CreateVector() {
    MyVector v(1000);

    return v;
 }

 int main() {
    MyVector vec = CreateVector();

    return 0;
 }




 

 

  

 

 

 

 

 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 Measurement Class: You can define a Measurement class 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 your Measurement class. This allows you to write intuitive code like Measurement 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 Measurement type.
    • 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.
  • Overloading Comparison Operators (<, >, ==): Similarly, you can overload comparison operators to compare two Measurement objects. 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 your Measurement objects, you can overload the << operator. This would let you print a measurement directly to the console like std::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.

 

OperatorMemberGlobalReturn
=T&
+=, -=, *=, /=T&
[]T& / const T&
()Any
++x, --xT&
x++, x--T
+, -, *, /Prefer globalT
==, !=Prefer globalbool
<<, >>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 (using delete) when you're done. For complex models with many data structures and functions, it's easy to forget to delete memory, 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_ptr and shared_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.

 

 // paterns to master

 auto model = std::make_unique<Model>();

 std::vector<std::unique_ptr<Model>>

 std::shared_ptr<MarketData>

 std::weak_ptr<Node>

 

 

 

class Matrix {
private:
    int data[2][2]{};

public:
    Matrix operator+(const Matrix& other) const {
        Matrix result;

        for (int i = 0; i < 2; ++i) {
            for (int j = 0; j < 2; ++j) {
                result.data[i][j] = data[i][j] + other.data[i][j];
            }
        }

        return result;
    }

    // Matrix + int
    Matrix operator+(int x) const {
        Matrix result;

        for (int i = 0; i < 2; ++i) {
            for (int j = 0; j < 2; ++j) {
                result.data[i][j] = data[i][j] + x;
            }
        }

        return result;
    }

    // int + Matrix
    friend Matrix operator+(int x, const Matrix& m) {
        return m + x;
    }
};


class Integer {
private:
    int value{};

public:
    Integer() = default;
    explicit Integer(int v) : value(v) {}

    void SetValue(int v) {
        value = v;
    }

    int GetValue() const {
        return value;
    }

    // Integer + Integer
    Integer operator+(const Integer& other) const {
        return Integer(value + other.value);
    }

    // Integer + int
    Integer operator+(int x) const {
        return Integer(value + x);
    }
};

// int + Integer
Integer operator+(int x, const Integer& y) {
    return Integer(x + y.GetValue());
}

/* /////////////////////////////////// */

/*For a dynamically sized Matrix,
I would avoid raw pointers
and manage memory with RAII containers such as std::vector<int>.*/

#include <vector>
#include <stdexcept>
#include <cstddef>

class Matrix {
private:
    std::size_t rows{};
    std::size_t cols{};
    std::vector<int> data;

public:
    Matrix() = default;

    Matrix(std::size_t r, std::size_t c, int initial_value = 0)
        : rows(r), cols(c), data(r * c, initial_value)
    {}

    int& operator()(std::size_t i, std::size_t j) {
        return data[i * cols + j];
    }

    const int& operator()(std::size_t i, std::size_t j) const {
        return data[i * cols + j];
    }

    std::size_t Rows() const {
        return rows;
    }

    std::size_t Cols() const {
        return cols;
    }

    Matrix operator+(const Matrix& other) const {
        if (rows != other.rows || cols != other.cols) {
            throw std::invalid_argument("Matrix sizes must match");
        }

        Matrix result(rows, cols);

        for (std::size_t i = 0; i < data.size(); ++i) {
            result.data[i] = data[i] + other.data[i];
        }

        return result;
    }

    Matrix operator+(int x) const {
        Matrix result(rows, cols);

        for (std::size_t i = 0; i < data.size(); ++i) {
            result.data[i] = data[i] + x;
        }

        return result;
    }

    friend Matrix operator+(int x, const Matrix& m) {
        return m + x;
    }
};

int main() {
    Matrix A(10, 10, 1);
    Matrix B(10, 10, 2);

    Matrix C = A + B;
    Matrix D = A + 5;
    Matrix E = 5 + A;
}

 

 

 

 

 

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 where static_cast becomes 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_cast ensures 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 of static_cast and 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!

 

 

 

 

 

 

 

 

 

Entradas populares

Lo mas consultado

Entradas populares