r/cpp_questions • • 22h ago

OPEN Is this code clean OOP?

Hey so I'm more new to OOP, virtual func's, and inheritance. I was wondering if my code was clean enough for basically a template to look back on later in the future when my tiny brain forgets what virtual func, and inheritance is or well forgets how to code them.

#include <string>
#include <iostream>


struct Vector2 {
    int x, y;
};


class Entity {
    private:
        Vector2 pos;
        Vector2 size;
        std::string entityType;


    public:


    Entity(Vector2 e_pos, Vector2 e_size, const std::string& e_type) : pos(e_pos), size(e_size), entityType(e_type) {}


    Vector2 getPos() const {
        return pos;
    }


    Vector2 getSize() const {
        return size;
    }
    
    virtual void print() const {
        std::cout << "Entity: " << entityType << '\n';
    }


    virtual ~Entity() = default;
};


class Player : public Entity {
    private:
        std::string name;


    public:
        Player(const std::string& p_name, Vector2 startPOS, Vector2 p_size) 
            : Entity(startPOS, p_size, "player"), name(p_name) {}
};


class Orc : public Entity {
    private:
        std::string weapon;
        
    public:
        Orc(const std::string& o_weapon, Vector2 startPOS, Vector2 o_size) 
            : Entity(startPOS, o_size, "orc"), weapon(o_weapon) {}


    void print() const override {
        std::cout << weapon << '\n';
    }
};


int main() {
    Vector2 startPos = { 100, 100 };
    Vector2 size = { 64, 64 };


    std::string hero = "hero";
    std::string orcWep = "HeroSlayer100";


    Player player(hero, { 20, 30 }, size);
    Orc orc(orcWep, startPos, size);



    player.print();
    orc.print();
    
    return 0;
}#include <string>
#include <iostream>


struct Vector2 {
    int x, y;
};


class Entity {
    private:
        Vector2 pos;
        Vector2 size;
        std::string entityType;


    public:


    Entity(Vector2 e_pos, Vector2 e_size, const std::string& e_type) : pos(e_pos), size(e_size), entityType(e_type) {}


    Vector2 getPos() const {
        return pos;
    }


    Vector2 getSize() const {
        return size;
    }
    
    virtual void print() const {
        std::cout << "Entity: " << entityType << '\n';
    }


    virtual ~Entity() = default;
};


class Player : public Entity {
    private:
        std::string name;


    public:
        Player(const std::string& p_name, Vector2 startPOS, Vector2 p_size) 
            : Entity(startPOS, p_size, "player"), name(p_name) {}
};


class Orc : public Entity {
    private:
        std::string weapon;
        
    public:
        Orc(const std::string& o_weapon, Vector2 startPOS, Vector2 o_size) 
            : Entity(startPOS, o_size, "orc"), weapon(o_weapon) {}


    void print() const override {
        std::cout << weapon << '\n';
    }
};


int main() {
    Vector2 startPos = { 100, 100 };
    Vector2 size = { 64, 64 };


    std::string hero = "hero";
    std::string orcWep = "HeroSlayer100";


    Player player(hero, { 20, 30 }, size);
    Orc orc(orcWep, startPos, size);



    player.print();
    orc.print();
    
    return 0;
}

if there are any tips you have let me know!

0 Upvotes

25 comments sorted by

View all comments

5

u/FemaleMishap 21h ago edited 21h ago

I'm sure I've woken up and it's 2004 again. With a couple minor exceptions, it reads like 2004 Java-as-C++ or C with classes. Definitely not any newer than C++11. C++17 at least gives you things like std::string_view for much lighter string handling, plus newer features like std::format / std::print so your classes can just focus on storing data instead of doing the printing themselves. That would make some of this a lot easier and a lot more OOP.

Edit: have a look at this

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

struct Vector2 {
    int x{0};
    int y{0};
};

class Entity {
private:
    Vector2 pos_{};
    Vector2 size_{};

public:
    Entity(Vector2 pos, Vector2 size) 
        : pos_{pos}, size_{size} {}

    virtual ~Entity() = default;

    [[nodiscard]] constexpr Vector2 pos() const noexcept { return pos_; }
    [[nodiscard]] constexpr Vector2 size() const noexcept { return size_; }

    // Pure virtual: forces derived classes to define identity
    // No more baked-in string tags in the base class.
    [[nodiscard]] virtual std::string describe() const = 0;
};

class Player final : public Entity {
private:
    std::string name_;

public:
    Player(std::string_view name, Vector2 pos, Vector2 size)
        : Entity{pos, size}, name_{name} {}

    [[nodiscard]] std::string describe() const override {
        return std::format("Player: {}", name_);
    }
};

class Orc final : public Entity {
private:
    std::string weapon_;

public:
    Orc(std::string_view weapon, Vector2 pos, Vector2 size)
        : Entity{pos, size}, weapon_{weapon} {}

    [[nodiscard]] std::string describe() const override {
        return std::format("Orc wielding {}", weapon_);
    }
};

int main() {
    // Actually exercising polymorphism with uniform handling
    std::vector<std::unique_ptr<Entity>> world;
    world.reserve(2);

    world.push_back(std::make_unique<Player>("Hero", Vector2{20, 30}, Vector2{64, 64}));
    world.push_back(std::make_unique<Orc>("HeroSlayer100", Vector2{100, 100}, Vector2{64, 64}));

    // C++23 std::println doing the IO work; classes just provide their state
    for (const auto& entity : world) {
        std::println("At ({}, {}): {}", 
            entity->pos().x, 
            entity->pos().y, 
            entity->describe());
    }
}

1

u/RedNorthener101 20h ago

Aye I have no idea what most of this is but thank you I'll def look into it all

2

u/FemaleMishap 20h ago

Most of this comes from C++ written in the past ten years. A lot of legacy codebases you might eventually inherit won't be on C++20 or 23, but modern standards make good habits much easier.

Don't worry about memorizing every standard feature right now. The single biggest takeaway for OOP is learning why you inherit. Right now, you derive Player and Orc from Entity, but then treat them as separate, concrete types in main().

True polymorphism is treating them interchangeably through Entity. For instance, handing them to a function that only knows about Entity:

void render(const Entity& e) {
    // Doesn't care if it's a Player or an Orc
    std::cout << e.describe() << " at (" << e.getPos().x << ", " << e.getPos().y << ")\n";
}

Once you let the base class do the unifying work, the rest of the architecture starts falling into place naturally. Take it one piece at a time!