Using most container types, it isn't obvious what the elements represent
std::vector<double> circle{25, 15, 7}; // what is the radius?
Using a map
, we cannot mix data types
std::map<std::string, std::string> student {
{"name", "Pat"},
{"age", "23"}, // age is represented as string
};
If data integrity is not guaranteed, nasty bugs happen
struct Circle { double x, y, radius; };
Circle c { .x = 25.0, .y = 15.0, .radius = -7.5}; // invalid radius
The inability to guarantee the validity of an object is likely the worst downside of purely procedural approaches
In the real world, there is an infinite variation of data types
OOP enables you to model your own datatypes
class
class
class ClassName {
public: // private is the default for classes (public for structs)
ClassName(.); // constructors are supported
~ClassName(.); // destructors are supported
type pub_member_1;
type pub_member_2; // declare as many properties as needed
type pub_method1(param1, ...);
type pub_method2(.); // declare as many methods as needed
private: // these are only accessible within the class
type priv_member_1_;
type priv_member_2_; // declare as many private properties as desired
type priv_method1(param1, ...);
type priv_method2(.); // declare as many private methods as desired
};
Be consitent (eg. C++ Core Guidelines or Google C++ Style Guide)
Always explain the reasons for your decisions
public
before protected
before private
orderUsed to hide the state of a structured object inside a class
Prevents direct access to the data members by clients
Ensures that clients cannot violate the class invariant
The invariant can be tested
class
Circle#include <iostream>
class Circle {
public:
Circle() = default; // use the compiler-generated default constructor
Circle(double x, double y, double radius) : x{x}, y{y} {
this->radius(radius); // `this` allows to reuse `radius` as parameter
}
double x {0.0}; // use public member instead of trivial getters / setters
double y {0.0};
double radius() const { return radius_; } // getter;
void radius(double radius) { // setter ensures a valid circle
if (radius >= 0) { radius_ = radius; return; }
std::cerr << "not allowed to set negative radius" << std::endl;
}
double area() const;
double circumference() const;
private:
double radius_ {1.0}; // default value is better than ctor default argument
}; // end class definition with a semicolon
int main() {
Circle c;
std::cout << "radius: " << c.radius() << std::endl;
c.radius(1.5);
std::cout << "radius: " << c.radius() << std::endl;
c.radius(-1);
std::cout << "radius: " << c.radius() << std::endl;
Circle c2 {0, 0, -1};
std::cout << "c2.radius: " << c2.radius() << std::endl;
}
To use a class
in other files, declare it in a header
file
Classes are usually also defined in header files
Class methods are declared in the class definition but defined in a designated source file
#ifndef INT_VECTOR_HPP
#define INT_VECTOR_HPP
#include <iostream>
namespace ds { // namespace for our own data structures (ds)
class IntVector { // very simplified int vector class
public:
IntVector() = default; // compiler generated default constructor
~IntVector() { delete[] elements_; } // destructor
IntVector(const IntVector&) = delete; // no copy constructor
IntVector& operator=(const IntVector& other) = delete; // no copy assignment
void push_back(int value);
int pop_back();
std::size_t size() { return size_; } // optimized by compiler if in header
private:
std::size_t size_ = 0;
std::size_t space_ = 0;
int* elements_ = nullptr;
void resize(std::size_t new_space);
};
}
#endif // INT_VECTOR_HPP
Download int_vector.hpp
#include "int_vector.hpp"
#include <algorithm>
#include <iostream>
namespace ds {
void IntVector::resize(std::size_t new_space) {
if (new_space <= space_) return; // never shrink
int* new_elements = new int[new_space];
std::copy(elements_, elements_ + size_, new_elements);
delete[] elements_;
elements_ = new_elements;
space_ = new_space;
}
void IntVector::push_back(int value) {
if (space_ == 0) {
resize(8); // default size is 8
} else if (space_ == size_) {
resize(space_ * 2);
}
elements_[size_] = value;
++size_;
}
int IntVector::pop_back() {
if (!size_) {
std::cerr << "ERROR: cannot pop element of empty Vector" << std::endl;
return 0;
}
--size_;
return elements_[size_];
}
}
Download int_vector.cpp
class
The class
can be used from any other file
#include "int_vector.hpp"
#include <iostream>
int main(void) {
ds::IntVector v;
v.push_back(5);
v.push_back(1);
v.push_back(10);
std::cout << "last element: " << v.pop_back() << std::endl;
std::cout << "last element: " << v.pop_back() << std::endl;
std::cout << "last element: " << v.pop_back() << std::endl;
std::cout << "last element: " << v.pop_back() << std::endl;
}
Download int_vector_main.cpp
Compile the program with clang++ int_vector_main.cpp int_vector.cpp