C++ Object-Oriented Approach


Creative Commons License
This -C++ Object-Oriented Approach- tutorial is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License
Headlines
Attributes versus functions

Rule(s)

Example (Smalltalk)

get_x
    "return of _x"
    ^_x.
set_x: x
    "assign _x with x"
    _x := x.

Rule(s)

Example

// '.h' file
class My_class {
    public:
        My_class(float = 0.F); // This is imposed by the necessity of defining the value of the 'b' constant!
        float a; // To be avoided, absolutely!
        const float b; 
    private:
        double _c;
    …
};

// '.cpp' file(s):
My_class::My_class(float my_f) : b(my_f) /* 'b' constant setup */ { …

My_class mc;
mc.a = 1.F; // OK because 'public:'
// mc.b = 2.F; // Compilation error because 'const'
std::cout << mc.b << std::endl; // OK because 'public:'
// mc._c = 3.; // Compilation error because 'private:'
// std::cout << mc._c << std::endl; // Compilation error because 'private:'

Rule(s)

Example

// '.h' file
class My_class {
    …
    private:
        void _f();
    public:
        void g(); // '_f' is probably called inside 'g' and/or in another method…
};

// '.cpp' file:
My_class mc;
// mc._f(); // Compilation error because 'private:'
mc.g(); // OK because 'public:'

Class attributes and class functions

Rule(s)

Example

// '.h' file:
class Temperature {
public:
    static const float Min;
    …
};

// '.cpp' file:
const float Temperature::Min = -273.15F; // In Celsius

if(_value < Min) … // Or 'Temperature::Min' when one accesses this (public) attribute from outside the 'Temperature' class

Rule(s)

Example

// '.h' file:
class Leap_year_utility {
private:
    std::tm _time; // '#include <ctime>'
public:
    static int January();
    int february() const;
    …
    bool leap_year() const; // Result depends on '_time'
};

// '.cpp' file:
int Leap_year_utility::January() {
    return 31;
}
int Leap_year_utility::february() const {
    return leap_year() ? 29 : 28;
}
…
Visibility and encapsulation

Rule(s)

Example (non-encapsulation) Non-encapsulation.Cpp.zip 

#include <cstdio>
#include <ctime>

int main(int argc, char** argv) {

    std::time_t t = 0L; // One refers to the 'long' primtive type, which means "non-encapsulation" -> very bad idea!
    std::printf(std::asctime(std::gmtime(&t))); // January 1, 1970, 00:00:00 GMT
    t = -1L; // That's the reason why encapsulation is important. Doing this is permissive -> again, very bad idea!
    std::printf(std::asctime(std::gmtime(&t))); // This may work, but…

    return 0;
}

Variations on visibility

Rule(s)

Example Encapsulation.Cpp.zip 

// '.h' file:
class Real {
    private: // "implementation" part
        std::vector<char> _implementation; // Machine-based representation through a predefined type, i.e., 'std::vector' ('char' plays the role of byte)
    public: // "interface" part
        Real(std::string); // To create huge 'Real' objects beyond 'double' capacity
    …
};

// '.cpp' file:
Real r("99999999999999999999999999999999999999999999999999999");

Rule(s)

Example

// '.h' file:
class Prisoner {
    private:
        const std::string _file_number;
    public:
        Prisoner(const std::string&);
        bool equals(const Prisoner&) const;
};

// '.cpp' file:
Prisoner::Prisoner(const std::string& file_number) : _file_number(file_number) {
}

bool Prisoner::equals(const Prisoner& p) const {
// 'std::string' comparison: http://en.cppreference.com/w/cpp/string/basic_string/compare
    if (this->_file_number.compare(p._file_number) != 0) return false; // No compilation error while '_file_number' is a priori inaccessible!
    return true;
}

Friend functions

Rule(s)

Example Friend.Cpp.zip 

// '.h' file:
#ifndef _Friend_h
#define _Friend_h

#include <string>

class B; // Forward reference

class A {
public:
    void f(const B&) const;
};

class B {
    int _i;
    friend void A::f(const B&) const;
};

class Reader; // Forward reference

class Phrase; // Forward reference

class Expression {
    std::string _content;
public:
    Expression(const std::string&);
private:
    std::string _read() const;
    friend Phrase operator+(const Phrase&, const Expression&); // Here, the '+' operator is not a member function
    friend class Reader;
};

class Phrase {
    const std::string _content;
public:
    Phrase(const std::string&);
    friend Phrase operator+(const Phrase&, const Expression&); // Here, the '+' operator is not a member function
    friend class Reader;
};

class Reader {
public:
    std::string read(const Expression&) const;
    std::string read(const Phrase&) const;
};

#endif
// '.cpp' file: 
#include <iostream>

#include "Friend.h"

void A::f(const B& b) const {
    b._i;
}

Expression::Expression(const std::string& s) : _content(s) {
}

std::string Expression::_read() const {
    return _content;
}

Phrase::Phrase(const std::string& s) : _content(s) {
}

Phrase operator+(const Phrase& p, const Expression& e) {
    Phrase result(p._content + e._read()); // Access to properties of 'Expression' and 'Phrase'
    return result;
}

std::string Reader::read(const Expression& e) const { // Access to private properties of 'Expression'
    return e._content;
}

std::string Reader::read(const Phrase& p) const { // Access to private properties of 'Phrase'
    return p._content;
}

int main(int argc, char** argv) {
    Reader FranckBarbier;
    Expression e("Caution! Avoid it... What?");
    Phrase p("The 'friend' keyword... ");
    std::cout << FranckBarbier.read(e) << '\n';
    std::cout << FranckBarbier.read(p) << '\n';
    std::cout << FranckBarbier.read(operator+(p, e)) << '\n';

    return 0;
}
Constructor and destructor

Rule(s)

Example

#ifndef _Evaluation_test_h
#define _Evaluation_test_h

class Evaluation_test { 
    int _size;
    double* _scores;
public:
    Evaluation_test(int);
    ~Evaluation_test();
    …
};

#endif
Evaluation_test::Evaluation_test(int size) : _size(size) {
    assert(_size > 0);
    _scores = new double[_size]; // 'new' operator
    std::time_t t;
    std::srand(std::time(&t));
    for (int i = 0; i < _size; i++) _scores[i] = (double) std::rand();
}

Evaluation_test::~Evaluation_test() {
    delete[] _scores; // 'delete' operator to cancel effect of 'new' in 'Evaluation_test' constructor
}

Overloading and delegation

Rule(s)

Example Alcatel_case_study.Cpp.zip 

// '.h' file:
class Limited_bit_stream {
…
// Constructors' overloading (as in Java, overloading applies to other functions as well):
public:
    Limited_bit_stream();
    Limited_bit_stream(const char[]);
    Limited_bit_stream(Bit_stream_format);
    …
};

// '.cpp' file:
Limited_bit_stream::Limited_bit_stream(const char a_string[]) : Limited_bit_stream() { … // From C++11, constructors may call others: this is named "constructor delegation"

default and delete

Rule(s)

Example Default_delete.Cpp.zip 

'.h' file:
class My_class { // 'inline' is for compactness only!
private:
    // 'std::is_trivially_copyable<My_class>: false':
    // 'std::is_trivially_assignable<My_class, My_class>': false:
    // 'std::is_trivially_move_constructible<My_class>': false:
    std::string _resource;
    // To prevent verbose writing of 'My_class::My_class() {}' in '.cpp' file:
    My_class() = default; // 'std::is_trivially_constructible<My_class>': false
public:

    // 'std::is_constructible<My_class, std::string>': true':
    inline My_class(std::string s) : _resource(s) {};

    // 'std::is_copy_constructible<My_class>': false:
    // 'std::is_assignable<My_class, My_class>': false:
    // 'std::is_move_constructible<My_class, My_class>': true:
    inline My_class(My_class&& mc) : _resource(std::move(mc._resource)) {}

    My_class(const My_class&) = delete; // 'std::is_copy_constructible<My_class>': false && 'std::is_move_constructible<My_class, My_class>': false
    My_class& operator=(const My_class&) = delete; // 'std::is_assignable<My_class, My_class>': false

    // 'std::is_assignable<My_class, My_class>': true:
    inline My_class& operator=(My_class&& mc) { // Move assignment
        _resource = std::move(mc._resource);
        return *this;
    };

    // To prevent verbose writing of 'My_class::~My_class() {}' in '.cpp' file:
    virtual ~My_class() = default; // Polymorphic type => !triviality
};

// '.cpp' file:
My_class mc("mc");

std::cout << std::boolalpha << "'std::is_trivially_constructible<My_class>': " <<
            std::is_trivially_constructible<My_class>::value << std::endl; // 'false'
std::cout << std::boolalpha << "'std::is_constructible<My_class, int>': " <<
            std::is_constructible<My_class, std::string>::value << std::endl << std::endl; // 'true'

std::cout << std::boolalpha << "'std::is_trivially_copyable<My_class>': " <<
            std::is_trivially_copyable<My_class>::value << std::endl; // 'false'
//    My_class mc_ = mc; // Compilation error because copy constructor has been deleted
std::cout << std::boolalpha << "'std::is_copy_constructible<My_class>': " <<
            std::is_copy_constructible<My_class>::value << std::endl << std::endl; // 'false'

My_class mc_("mc_");
//    mc = mc_; // Compilation error because copy assignment has been deleted
mc = std::move(mc_); // Move assignment
mc = My_class("Move assignment"); // Move assignment

std::cout << std::boolalpha << "'std::is_trivially_assignable<My_class, My_class>': " <<
            std::is_trivially_assignable<My_class, My_class>::value << std::endl; // 'false'
std::cout << std::boolalpha << "'std::is_assignable<My_class, My_class>': " <<
            std::is_assignable<My_class, My_class>::value << std::endl << std::endl; // 'true'

My_class mc__(My_class("Move constructor"));
std::cout << std::boolalpha << "'std::is_trivially_move_constructible<My_class>': " <<
            std::is_trivially_move_constructible<My_class>::value << std::endl; // 'false'
std::cout << std::boolalpha << "'std::is_move_constructible<My_class>': " <<
            std::is_move_constructible<My_class>::value << std::endl; // 'true'

Example (stupid people?)

class X {
public:
    ~X() = delete;
};

// Later on, in '.cpp' file:
// X x; // Compilation error: somehow late but safe anyway...
Operator overloading

Rule(s)

Example

// '.h' file:
class Polynomial {
    …
public:
    …
    Polynomial operator *(const Polynomial&) const;
    …
};

// '.cpp' file:
Polynomial a, b;
// 'a' and 'b' are filled…
Polynomial c = a * b; // Alternative notation: 'c = a.operator *(b);'

Example

// '.h' file:
class Elephant : public Elephantidae {
    …
    short _weight;
public:
    …
    bool operator<(const Elephant&) const; 
};

// '.cpp' file:
bool Elephant::operator<(const Elephant& e) const {
    return this->_weight < e._weight;
}
Inheritance

Rule(s)

Example of inheritance tree (in French)

Structural inheritance

Example Inheritance_polymorphism.Cpp.zip 

class Compte_bancaire {
        int _id;
    protected:
        float _solde;
    …

class Compte_cheque : public Compte_bancaire {
    protected:
        float _taux_interet;
        float _seuil;
    …

Behavioral inheritance

Example Inheritance_polymorphism.Cpp.zip 

class Compte_bancaire {
    …
    public:
        inline int id() const {return _id;} // This function is usable by 'Compte_cheque' objects!
    …

Property extension

Example Inheritance_polymorphism.Cpp.zip 

// '.h' file:
class Compte_epargne_logement : public Compte_epargne { 
    public:
        const static float Taux_interet; // Fixed rate in the French law
    public:
        virtual float taux_interet() const;
    …

// '.cpp' file:
const float Compte_epargne_logement::Taux_interet = Livret_A::Taux_interet * 2.F / 3.F;
float Compte_epargne_logement::taux_interet() const {
    return Taux_interet;
}
…

Inheritance and constructors

Example Inheritance_polymorphism.Cpp.zip 

// '.h' file:
class Compte_epargne : public Compte_bancaire {
    protected:
        Compte_epargne(int,float = 0.F);
};

// '.cpp' file:
Compte_epargne::Compte_epargne(int id,float solde) : Compte_bancaire(id,solde) {}

Overriding (redefinition)

Example Inheritance_polymorphism.Cpp.zip 

// '.h' file:
class Compte_bancaire {
    …
    public:
        …
        virtual float taux_interet() const = 0; // Abstract function
        virtual void appliquer_taux_interet();
};

class Compte_cheque : public Compte_bancaire {
    …
    public:
        …
        virtual float taux_interet() const; // Abstract nature is removed when overriding
        virtual void appliquer_taux_interet(); // Overriding as well
};

// '.cpp' file:
void Compte_bancaire::appliquer_taux_interet() {
    _cumul_interets = _solde * (1.F + (taux_interet() / 100.F));
}
float Compte_cheque::taux_interet() const {
    return _taux_interet;
}
void Compte_cheque::appliquer_taux_interet() {
    if(_solde > _seuil) Compte_bancaire::appliquer_taux_interet();
}

Accessing properties of ancestor classes

Example

// Assumption: 'Daughter' inherits from 'Mother', which inherits from 'Grandmother'. One wants in 'Daughter' to access to 'jewel' (not 'private') in 'Mother':
Mother::jewel;
// One now wants in 'Daughter' to access to 'other_jewel' (not 'private') in 'Grandmother':
Grandmother::other_jewel;

final and override keywords

Rule(s)

Example (inheriting with public from the predefinedstd::stack C++ component -#include <stack>-)

template <typename T> class Bound_stack : public std::stack<T> {
private:
    const typename std::stack<T>::size_type _capacity;
public:
    Bound_stack(const typename std::stack<T>::size_type&);
    bool full() const;
    void push(const T&);
};

template <typename T> Bound_stack<T>::Bound_stack(const typename std::stack<T>::size_type& capacity) : _capacity(capacity) {
}

template <typename T> bool Bound_stack<T>::full() const {
    return _capacity == this->size();
}

template <typename T> void Bound_stack<T>::push(const T& t) {
    if (full()) throw "push";
    std::stack<T>::push(t);
}
std::stack<char>* bug = new Bound_stack<char>(1); // 'private' inheritance prevents such an assignment
bug->push('a');
bug->push('b'); // Since 'push' is not virtual in 'Bound_stack' then 'push' from 'stack' (the type of 'bug') is called
delete bug; // 'stack' destructor is called while 'bug' effectively points to a 'Bound_stack' object

Rule(s)

Example (not using final) Final_override_keywords.Cpp.zip 

class Secret {
private:
    virtual void cypher();
public:
    void send_message();
};

class Hack : public Secret {
private:
    void cypher() override; // Check "true" overriding, compiler error otherwise!
};
void Secret::cypher() {
    std::cout << "Secret: let's cypher…" << std::endl;
}
void Secret::send_message() {
    cypher();
}
void Hack::cypher() {
    std::cout << "Hack: let's bypass 'cypher'!" << std::endl;
}
…
Hack* h = new Hack; // Don't forget to use 'delete' later on!
h->send_message(); // 'Hack: let's bypass 'cypher'!' is displayed

Example (using final)

class Secret {
private:
    virtual void cypher() final; // Compilation error in 'Hack' when attempting to override 'cypher'
    …

Final class

Rule(s)

Example

class Temperature final { …

Inheritance and visibility

Rule(s)

Example Inheritance_and_visibility.Cpp.zip 

std::string C::format() const {
    // A::_a; // Error because 'A::_a' is 'private'
    // B::_a; // Error because 'B::_a' is 'private'
    // A::_b; // Error because 'class B : private A'
    // A::c; // Error because 'class B : private A'
    return B::_b + B::c + _a + _b + c;
}

std::string D::format() const {
    // A::_a; // Error because 'A::_a' is 'private'
    // B::_a; // Error because 'B::_a' is 'private'
    // C::_a; // Error because 'C::_a' is 'private'
    // A::_b; // Error because 'class B : private A'
    // A::c; // Error because 'class B : private A'
    return C::_b + C::c + B::_b + B::c + _a + _b + c;
}

int main() {
    D* d = new D;
    std::cout << d->format() << std::endl;
    C* c = d;
    std::cout << c->format() << std::endl; 
    // B* b = d; // Error because 'class C : protected B'
    // std::cout << d->format() << std::endl;
    // A* a = d; // Error because 'class B : private A'
    // std::cout << a->format() << std::endl;
    delete d;
    return 0;
}
Polymorphism

Rule(s)

Example (pointers):

Compte_cheque cc(1963,10500.F,2.F,10000.F);
Compte_bancaire* cb = &cc; // 'cb' points to 'cc' since their mutual types are compatible through inheritance
cb->appliquer_taux_interet(); // 'appliquer_taux_interet' in 'Compte_cheque' is run since the runtime type of 'cb' is 'Compte_cheque'
cb = &cel; // Assumption: 'cel' is a direct instance of 'Compte_epargne_logement'
cb->appliquer_taux_interet(); // Polymorphism again

Example (references):

Compte_bancaire& cb = cc;
cb.appliquer_taux_interet();
cb = cel;
cb.appliquer_taux_interet();

Rule(s)

Example

Animal* a1 = new Cat; // Assumption: 'Cat' directly or indirectly inherits from 'Animal'
Cat c;
Animal& a2 = c;

Polymorphism does not work in a constructor

Example (polymorphism fails) Inheritance_polymorphism.Cpp.zip 

// '.h' file:
class Note {
    …
private:
    virtual void initialization();
    …
public:
    Note(std::istringstream * const);
    …
};

class Confidential_note : public Note {
    …
private:
    void initialization();
public:
    Confidential_note(std::istringstream * const);
    …
};

// '.cpp' file:
void Note::initialization() {
    …
}
// Contrary to Java, event though 'Note::initialization' is declared 'virtual', polymorphism cannot apply because the call occurs inside the constructor!
Note::Note(std::istringstream * const source) : _source(source) {
    initialization(); 
}
void Confidential_note::initialization() {
    … // Polymorphic behavior, which may possibly include 'Note::initialization();'
}
// One expects that, by polymorphism, 'Confidential_note::initialization' is called -> WRONG!
Confidential_note::Confidential_note(std::istringstream * const source) : Note(source) {}

Example (mandatory adaptation) Inheritance_polymorphism.Cpp.zip 

// '.cpp' file:
…
Confidential_note::Confidential_note(std::istringstream * const source) : Note(source) {
    initialization(); // One must explicitely call 'Confidential_note::initialization'
}

Virtual destructor

Rule(s)

Example Virtual_destructor.Cpp.zip 

class Expression {
    …
    virtual ~Expression();
    …
class Sentence : public Expression {
    …
    virtual ~Sentence(); // For descendants of 'Sentence'
    …

// Later on, in '.cpp' file:
char separators[] = {' ', '-'};
Expression* e = new Sentence((char*) "Franck Barbier", separators);
…
delete e; // Good, destructor in 'Sentence' is called!

Covariance (on return types)

Example (no covariance) Covariance.Cpp.zip 

// 'Covariance.h' file
#ifndef _Covariance_H
#define _Covariance_H

class Individual {
public:
    virtual const Individual cloning() const;
};

class Man : public Individual {
public:
    const Man cloning() const; // Compilation error: virtual function 'cloning' has a different return type ('const Man') than the function it overrides (which has 'const Individual' as return type)
};

class Woman : public Individual {
public:
    const Woman cloning() const; // Same type of compilation error
};

#endif	/* _Covariance_H */

Example (covariance) Covariance.Cpp.zip 

// 'Covariance.h' file
#ifndef _Covariance_H
#define _Covariance_H

#include <iostream>

class Individual {
public:
    virtual const Individual* cloning() const;
    …
};

class Man : public Individual {
public:
    const Man* cloning() const;
    …
};

class Woman : public Individual {
public:
    const Woman* cloning() const;
    …
};

#endif	/* _Covariance_H */
// 'Main.cpp' file
#include <iostream>

#include "Covariance.h"

const Individual* Individual::cloning() const {
    std::cout << "'cloning()' in 'Individual' is called…\n";
    return new Individual; // Very bad programming because 'delete' must be later called in calling context
}

const Man* Man::cloning() const {
    std::cout << "'cloning()' in 'Man' is called…\n";
    return new Man; // Very bad programming because 'delete' must be later called in calling context
}

const Woman* Woman::cloning() const {
    std::cout << "'cloning()' in 'Woman' is called…\n";
    return new Woman; // Very bad programming because 'delete' must be later called in calling context
}

int main(int argc, char** argv) {
    const Individual *const FranckBarbier = new Man;
    const Individual* clone = FranckBarbier->cloning(); // 'cloning()' in 'Man' is called… => covariance, yes!
    delete clone;
    delete FranckBarbier;

    return 0;
}
Multiple inheritance

Rule(s)

Example

// '.h' file:
class Ornithorhynchus : public Mammal, public Oviparous { …
class Echidna : public Mammal, public Oviparous { …

Rule(s)

Example

class iostream : public istream, public ostream { // This is the standard definition in the '<iostream>' file
    …
};

Rule(s)

Example

class Animal {
    protected:
        std::list<Animal*> _kids;
    public:
        inline void Animal::birth(Animal* animal) {_kids.push_back(animal);} // Here, 'inline' is only for the sake of brevity!
    …

class Mammal : virtual public Animal { …
 
class Oviparous : virtual public Animal { …

Multiple inheritance and visibility

Rule(s)

Example Multiple_inheritance_and_visibility.Cpp.zip 

class A {
    public:
        virtual void f();
};
class B : public A {
    public:
        virtual void f();
};
class C : A {
    public:
        virtual void f();
};
class D : B,C {
    public:
        void g();
};

// Later on, in '.cpp' file:
void D::g() {
    A* pA1;
    // pA1 = this; // Compilation error because two paths exist from 'D' to 'A'
    pA1 = (B*)this; // Path is chosen, OK
    // pA1 = (C*)this; // Compilation error 'C' inherits from 'A' in a private mode
    pA1->f();
}