top of page

Learning C++ from Scratch

Let me start with something important.


I have no prior experience in C++.


I’m not coming from years of STL mastery.

I’m not refactoring legacy engines.

I’m not optimizing template metaprogramming.


I’m studying C++ from zero.


And this small exercise — modeling a financial instrument with Stock and Future — forced me to understand concepts that no high-level language lets you ignore.


This isn’t about finance.


It’s about structure, ownership, invariants, and disciplined abstraction.

The Abstract Base Class


Instead of starting directly with Stock or Future, I defined an abstract base class.

That decision alone changes how you think.


class FinancialInstrument
public:
    explicit FinancialInstrument(const std::string& t)
        : ticker_(t), n_(0), pmc_(0.0f), direction_(FLAT), multiplier_(1.0f)

    {}

    virtual ~FinancialInstrument() = default;
    bool buy(unsigned int amount, float price);
    bool sell(unsigned int amount, float price, float* gain);
    bool sellshort(unsigned int amount, float price);
    bool exitshort(unsigned int amount, float price, float* gain);

    virtual float getMultiplier() const = 0;
    virtual void info() const = 0;
protected:
    float multiplier_;
private:
    std::string ticker_;
    unsigned int n_;
    float pmc_;
    direction_t direction_;
};

Two things matter here:

  • The destructor is virtual.

  • getMultiplier() and info() are pure virtual.


This makes the class abstract.


You cannot instantiate it.


That is intentional.


It forces a separation between interface (contract) and implementation (behavior).

Stock: The Simplest Specialization


A stock has multiplier = 1.


Nothing fancy.


class Stock: public FinancialInstrument
{
public:
    explicit Stock(const std::string& t)
        : FinancialInstrument(t)
    {
        multiplier_ = 1.0f;
    }
   float getMultiplier() const override
    {
        return multiplier_;
    }
    void info() const override
    {
        std::cout << "[STOCK] multiplier=" << multiplier_ << "\n";
    }
};

What matters here is not complexity.


It’s the use of:

  • override

  • virtual dispatch

  • inherited interface

C++ forces you to declare intent explicitly.

Future: Parameterized Multiplier


Unlike stocks, futures contracts have a multiplier defined externally.

class Future : public FinancialInstrument
{
public:
    Future(const std::string& t, float multiplier)
        : FinancialInstrument(t)
    {
       multiplier_ = multiplier;
    }
    float getMultiplier() const override
    {
        return multiplier_;
    }
    void info() const override
    {
        std::cout << "[FUTURE] multiplier=" << multiplier_ << "\n";
    }
};

Now behavior diverges.

But the interface remains identical.

That is polymorphism done intentionally.

Extending the Model: Unrealized PnL


To make the class meaningful, I extended it with unrealized PnL:


float unrealizedPnL(float currentPrice) const
{
    if(direction_ == LONG)
        return (currentPrice - pmc_) * n_ * multiplier_;

    if(direction_ == SHORT)
        return (pmc_ - currentPrice) * n_ * multiplier_;

    return 0.0f;
}

What this snippet teaches:

  • State must be handled explicitly.

  • LONG and SHORT logic must be symmetric.

  • Multiplier matters.

  • Edge cases must be defined.


There is no runtime magic correcting mistakes.

You must encode every branch yourself.

Runtime Polymorphism in Action


Now the interesting part.


std::vector<std::unique_ptr<FinancialInstrument>> portfolio;
portfolio.push_back(std::make_unique<Stock>("AAPL"));
portfolio.push_back(std::make_unique<Future>("MES", 5.0f));
for(const auto& inst : portfolio)
{
    inst->info();
}

This works because:

  • The destructor is virtual.

  • The interface is pure virtual.

  • Ownership is encoded via std::unique_ptr.


Without virtual ~FinancialInstrument(), this design would be unsafe.

Without unique_ptr, ownership would be ambiguous.


C++ doesn’t let you pretend these details don’t matter.

What This Small Hierarchy Actually Teaches


Even this simple example forces you to confront:

  • Lifetime management

  • Ownership semantics

  • Interface vs implementation

  • Compile-time guarantees

  • Runtime dispatch

  • State invariants


When you are learning from scratch, you can’t rely on intuition.


You must reason everything out.


And that’s where the value lies.

A Subtle but Important Realization


Inheritance is not polymorphism.


Encapsulation is not secrecy.


Virtual is not design quality.


Templates are not “just faster”.


Every keyword in C++ encodes a structural choice.


And if you don’t understand the cost of that choice, the language will expose it.

Final Reflection


Studying C++ from zero is uncomfortable.


It removes abstraction cushions.


It forces you to think about:

  • who owns what

  • when things die

  • how state changes

  • where variability is resolved

  • how concurrency affects structure


But that discomfort is productive.


Because once you understand these mechanisms, you don’t just write C++ differently.


You design systems differently.


And that is exactly the kind of discipline that belongs in ^Algo.*$.


Commenti


bottom of page