Rule(s)
- In OO programming, attributes aim at being hidden (Parnas' information hiding principle) to avoid any direct (unsafe) access from any code outside data structures.
- In contrast, functions are generally exposed. Only internal (reusable) computations are hidden, leading to inherent (private) functions.
- The Smalltalk programming language philosophy is interesting: attributes are by construction not accessible while functions are. To create some access (read and/or write), getters and setters are required.
Example (Smalltalk)
get_x "return of _x" ^_x. set_x: x "assign _x with x" _x := x.
Rule(s)
- C++ supports the encapsulation principle with the
private
andprotected
keywords. There is no reason at all to set attributes to thepublic
visibility except constant attributes (const
keyword).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)
- Functions in C++ are either visible from outside or for internal use only (
private
modifier).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)
- Class attributes are identified by the
static
keyword. Instead of “instance attributes”, values of class attributes are shared among instances. In other words, class attributes belong to the class itself and the class' instances just get an access.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)
- Class functions are also identified by the
static
keyword. Instead of “instance functions”, class functions only deal with class attributes.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; } …
Rule(s)
- Visibility and encapsulation are such that the hidden part is called “the implementation” while the visible part is called “the interface”. Visibility possibilities are based on the
private
,protected
(in relation with inheritance) andpublic
keywords.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)
- User-defined types benefit from being based on existing types (primitive or not) whose visibility is void.
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)
- Contrary to Eiffel,
private
in C++ does not really mean “private”!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)
- Friend functions tend to attenuate visibility and encapsulation barriers in a controlled manner.
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; }
Rule(s)
- In C++, multiple constructors may exist (overloading) while it exists only one destructor without parameters. The latter can be declared as
virtual
.- By default, for any class, C++ automatically generates one destructor and one constructor without parameters. Introducing a constructor (with or without parameters), respectively a destructor, erases that provided by C++.
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)
- C++ comes with the notion of “constructor delegation”: constructor calls are chained.
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
anddelete
Rule(s)
- The
default
anddelete
keywords may, from C++11, be associated with “implicit” functions: constructors (base, copy, move), destructor and assignment (copy, move).- Triviality like TriviallyCopyable is the general-purpose mechanism for assigning/copying/moving an object to another. Triviality is lost as soon as the type of interest, e.g.,
My_class
, has, for instance, a resource attribute to be assigned/copied/moved, e.g.,std::string _resource;
, which is itself not trivially copyable.- Beyond, if the type of interest is polymorphic (through its destructor, for instance) then
std::is_polymorphic<My_class>
⇒ !std::is_trivially_copyable<My_class>
, !std::is_trivially_assignable<My_class>
, and !std::is_trivially_move_constructible<My_class>
.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...
Rule(s)
- Basic C++ operators as
+
,-
,*
,/
,<
,>
,()
,[]
… may be contextually redefined for a given class.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; }
Rule(s)
- Inheritance is a foundation of OO programming. The principle behind inheritance is the fact that data structures are extended from existing ones. This amounts to adding attributes (structural inheritance) and/or functions (behavioral inheritance). The way of dealing with inheritance in C++ is the use of the
:
sign. Note that C++ supports multiple inheritance between classes.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
andoverride
keywordsRule(s)
- Originally, C++ lets us the possibility of freely overriding functions, a potential problem. More recently,
final
andoverride
keywords allow us to design safer inheritance hierarchies.- In figure, inheriting with
public
visibility from the predefinedstd::stack
STL component (left-hand side) is a source of bug. Instead, inheriting withprivate
(right-hand side) is secure.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)
override
ensures that the function is virtual and is overriding a virtual function from a base class. The code is ill-formed (compilation error) if this untrue.Example (not using
final
) Final_override_keywords.Cpp.zipclass 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)
- From C++11, classes may be declared as not “inheritable” using
final
.Example
class Temperature final { …
Inheritance and visibility
Rule(s)
- Inheritance links may have visibility flags.
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; }
Rule(s)
- Polymorphism is intimately associated with inheritance. Polymorphism relies on the testing of objects' type at run-time to choose the “right” code. Contrary to Java, polymorphism is by default disabled in C++. The
virtual
keyword must then be used to enact polymorphism.- C++ imposes the use of pointers or references to play polymorphism, but references are preferable.
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)
- Polymorphism is strongly grounded on the fact that “higher” types in the hierarchy may dynamically “point/refer” to lower ones, e.g., a cat is a an animal.
- Covariance is the fact that
transform(a cat)
is a (still)transform(an animal)
. Contravariance is the opposite.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)
- A virtual destructor allows the polymorphic call of a destructor. So, the call depends upon the runtime type of the destroyed object.
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; }
Rule(s)
- Multiple inheritance is the possibility of having more than one direct mother class. Multiple inheritance is difficult to handle even though concrete modeling situations may call for. Nonetheless, almost all OO languages do not support it for classes.
Example
// '.h' file: class Ornithorhynchus : public Mammal, public Oviparous { … class Echidna : public Mammal, public Oviparous { …
Rule(s)
- Multiple inheritance is used in core C++ libraries.
Example
class iostream : public istream, public ostream { // This is the standard definition in the '<iostream>' file … };
Rule(s)
- Potential problems (a.k.a. “multiple inheritance conflicts”) come from the cohabitation between structural and behavioral inheritance. On purpose, C++ supports
virtual
inheritance (caution:virtual
inheritance is not related at all to polymorphism!).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)
- Mixing multiple inheritance and visibility may lead to tricky situations.
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(); }