Mentions légales du service

Skip to content
Snippets Groups Projects
Commit 5aa17e4e authored by GILLES Sebastien's avatar GILLES Sebastien
Browse files

Minor corrections + remove cells already executed.

parent 8b83636e
No related branches found
No related tags found
No related merge requests found
%% Cell type:markdown id: tags:
# [Getting started in C++](/) - [Object programming](/notebooks/2-ObjectProgramming/0-main.ipynb) - [(Base) constructors and destructor](/notebooks/2-ObjectProgramming/3-constructors-destructor.ipynb)
%% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Introduction-to-base-constructor" data-toc-modified-id="Introduction-to-base-constructor-1">Introduction to base constructor</a></span></li><li><span><a href="#Initializing-a-reference-data-attribute" data-toc-modified-id="Initializing-a-reference-data-attribute-2">Initializing a reference data attribute</a></span></li><li><span><a href="#Delegating-constructor" data-toc-modified-id="Delegating-constructor-3">Delegating constructor</a></span></li><li><span><a href="#Default-constructor" data-toc-modified-id="Default-constructor-4">Default constructor</a></span></li><li><span><a href="#Good-practice:-data-attribute-initialization" data-toc-modified-id="Good-practice:-data-attribute-initialization-5">Good practice: data attribute initialization</a></span></li><li><span><a href="#Good-practice:-use-explicit-constructors-by-default" data-toc-modified-id="Good-practice:-use-explicit-constructors-by-default-6">Good practice: use <code>explicit</code> constructors by default</a></span></li><li><span><a href="#Destructor" data-toc-modified-id="Destructor-7">Destructor</a></span><ul class="toc-item"><li><span><a href="#Default-destructor" data-toc-modified-id="Default-destructor-7.1">Default destructor</a></span></li></ul></li></ul></div>
%% Cell type:markdown id: tags:
## Introduction to base constructor
In fact, our previous `init()` function is meant to be realized through a dedicated method called a **constructor**. By convention, a constructor shares the name of the struct or class.
Several constructors may be defined for a given class, provided there is no signature overlap.
%% Cell type:code id: tags:
``` C++17
struct Vector
{
double x_;
double y_;
double z_;
Vector(); // Constructor
Vector(double x, double y, double z); // Another constructor
double norm() const;
};
```
%% Cell type:markdown id: tags:
Data attributes may be initialized more efficiently with a special syntax shown below:
%% Cell type:code id: tags:
``` C++17
Vector::Vector(double x, double y, double z)
: x_(x), // See the syntax here: `:` to introduce data attributes initialization,
y_(y), // and commas to separate the different data attributes.
z_(z)
{ }
```
%% Cell type:code id: tags:
``` C++17
Vector::Vector()
: x_(0.), //
: x_0.),
y_(0.),
z_(0.)
{ }
```
%% Cell type:code id: tags:
``` C++17
double Vector::norm() const
{
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_);
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
Vector v(5., 6., -4.2); // note the creation of an object with a constructor call.
std::cout << v.norm() << std::endl;
}
```
%% Cell type:markdown id: tags:
**WARNING:** There is a technicality for constructor without arguments: they must be called without parenthesis (the reason is a possible confusion with a [functor](/notebooks/3-Operators/5-Functors.ipynb) - see Item 6 of \cite{Meyers2001} or [this blog post](https://www.fluentcpp.com/2018/01/30/most-vexing-parse/) if you want to learn more about the reasons of this):
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
Vector v; // no parenthesis here!
std::cout << v.norm() << std::endl;
}
```
%% Cell type:markdown id: tags:
## Initializing a reference data attribute
We saw above a new syntax to initialize data attributes, but there is no harm defining the same as we did in our former `init()` method. That is however not true when there is a reference data attribute, for which the reference must absolutely be defined with the new syntax:
%% Cell type:code id: tags:
``` C++17
struct First
{
};
```
%% Cell type:code id: tags:
``` C++17
struct Second
{
const First& first_;
Second(const First& first);
};
```
%% Cell type:code id: tags:
``` C++17
Second::Second(const First& first)
{
first_ = first; // COMPILATION ERROR: can't initialize a reference data attribute here!
}
```
%% Cell type:code id: tags:
``` C++17
Second::Second(const First& first)
: first_(first) // OK!
{ }
```
%% Cell type:markdown id: tags:
The reason for this behaviour is that the reference must be defined at the very moment the object is created, and in fact the body of a constructor is run _after_ the actual creation occurs. With the proper syntax, the data is properly initialized in the same time the object is created.
## Delegating constructor
Since C++ 11, it is possible to use a base constructor when defining another constructor:
%% Cell type:code id: tags:
``` C++17
struct Vector2
{
double x_, y_, z_;
Vector2();
Vector2(double xyz);
Vector2(double x);
Vector2(double x, double y, double z);
void Print() const;
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void Vector2::Print() const
{
std::cout << "(x, y, z) = (" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
Vector2::Vector2()
: x_(-1.),
y_(-1.),
z_(-1.)
{
std::cout << "Calling Vector2 constructor with no arguments." << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
Vector2::Vector2(double x, double y, double z)
: x_(x),
y_(y),
z_(z)
{ }
```
%% Cell type:code id: tags:
``` C++17
Vector2::Vector2(double x)
: Vector2()
{
x_ = x; // As the first constructor is assumed to build fully the object, you can't assign data attributes
// before the body of the constructor.
}
```
%% Cell type:code id: tags:
``` C++17
{
std::cout << "Constructor with no argument:" << std::endl;
Vector2 v1;
v1.Print();
std::cout << "Constructor with no delegation:" << std::endl;
Vector2 v2(3., 7., 5.);
v2.Print();
std::cout << "Constructor that calls a delegate constructor:" << std::endl;
Vector2 v3(3.);
v3.Print();
}
```
%% Cell type:markdown id: tags:
## Default constructor
If in a `class` or `struct` no constructor is defined, a default one is assumed: it takes no argument and sports an empty body.
As soon as another constructor is defined, this default constructor no longer exists:
%% Cell type:code id: tags:
``` C++17
struct ClassWithoutConstructor
{ };
```
%% Cell type:code id: tags:
``` C++17
{
ClassWithoutConstructor my_object;
}
```
%% Cell type:code id: tags:
``` C++17
struct ClassWithConstructorWithArg
{
ClassWithConstructorWithArg(int a);
};
```
%% Cell type:code id: tags:
``` C++17
ClassWithConstructorWithArg::ClassWithConstructorWithArg(int a)
{ }
```
%% Cell type:code id: tags:
``` C++17
{
ClassWithConstructorWithArg my_object; // COMPILATION ERROR!
}
```
%% Cell type:markdown id: tags:
This must seem messy at first sight, but doing otherwise would be nightmarish: if you define a very complex class that must be carefully initialized with well-thought arguments, you do not want your end-user to bypass this with a inconsiderate call to a constructor without arguments!
If you want to enable back constructor without arguments, you may do so by defining it explicitly. C++11 introduced a nice way to do so (provided you wish an empty body - if not define it explicitly yourself):
%% Cell type:code id: tags:
``` C++17
struct ClassWithConstructorWithAndWithoutArg
{
ClassWithConstructorWithAndWithoutArg() = default;
ClassWithConstructorWithAndWithoutArg(int a);
};
```
%% Cell type:code id: tags:
``` C++17
ClassWithConstructorWithAndWithoutArg::ClassWithConstructorWithAndWithoutArg(int a)
{ }
```
%% Cell type:code id: tags:
``` C++17
{
ClassWithConstructorWithAndWithoutArg my_object; // OK!
}
```
%% Cell type:markdown id: tags:
## Good practice: data attribute initialization
In a constructor, you are expected to initialize properly all the data attributes. You can't expect a default behaviour if you fail to do so:
%% Cell type:code id: tags:
``` C++17
struct BadlyInitialized
{
int a;
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
BadlyInitialized my_object;
std::cout << "Undefined behaviour: no guarantee for the value of the data attribute!: " << my_object.a << std::endl;
}
```
%% Cell type:markdown id: tags:
You are therefore supposed to define explicitly all the data attributes in all of your constructors. It was easy to get trumped by this in C++98/03: if you added a new data attribute and forgot to initialize it in one of your constructor, you would have undefined behaviour that is one of the worst bug to track down! (as on your machine/architecture you may have a "good" behaviour haphazardly).
Fortunately, C++ 11 introduced a mechanism I strongly recommend to provide a default value:
%% Cell type:code id: tags:
``` C++17
struct SafeClass
{
int a_ = 5; // The default value is provided here in the class declaration.
int a_ { 5 }; // The default value is provided here in the class declaration.
SafeClass() = default;
SafeClass(int new_value);
};
```
%% Cell type:code id: tags:
``` C++17
SafeClass::SafeClass(int new_value)
: a_(new_value)
{ }
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
SafeClass no_Arg;
std::cout << "If constructor doesn't change the value, default is used: " << no_Arg.a_ << std::endl;
SafeClass modified(10);
std::cout << "If constructor changes the value, choice is properly used: " << modified.a_ << std::endl;
}
```
%% Cell type:markdown id: tags:
Please notice doing so doesn't prevent you to use the efficient initialization of `a_` in the constructor with arguments: the values thus provided in the data attributes definitions are used only in the constructor doesn't supersede them.
%% Cell type:markdown id: tags:
In the same spirit, if you get pointers as data attributes it is a good idea to set them by default to `nullptr`: this way you may check with an [`assert`](/notebooks/5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb#Assert) it has been correctly initialized before use.
## Good practice: use `explicit` constructors by default
Let's study the following case:
%% Cell type:code id: tags:
``` C++17
struct ClassWithIntConstructor
{
ClassWithIntConstructor(int a);
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
ClassWithIntConstructor::ClassWithIntConstructor(int a)
{
std::cout << "Constructor called with argument " << a << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
ClassWithIntConstructor my_object(5);
my_object = 7; // Dubious but correct: assigning an integer!
}
```
%% Cell type:markdown id: tags:
So what happens here? In fact, the compiler implicitly convert the integer read into a constructor with an integer argument...
There are situations in which this might be deemed the right thing to do (none to my mind but I guess it depends on your programming style) but more often than not it's not what is intended and a good old compiler yell would be much preferable.
To do so, in C++ 11 you must stick the keyword **explicit** in the declaration in front of the constructor. Personally I tend to always provide it to my constructors, following the likewise advice by \cite{Meyers2015}.
%% Cell type:code id: tags:
``` C++17
struct ClassWithExplicitIntConstructor
{
explicit ClassWithExplicitIntConstructor(int a);
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
ClassWithExplicitIntConstructor::ClassWithExplicitIntConstructor(int a)
{
std::cout << "Constructor called with argument " << a << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
ClassWithExplicitIntConstructor my_object(5);
my_object = 7; // COMPILATION ERROR! YAY!
}
```
%% Cell type:markdown id: tags:
## Destructor
%% Cell type:markdown id: tags:
The pendant of the constructor is the **destructor**, which is called when the object is terminated. Contrary to constructors, there is only one destructor for a given class, and by design it takes no parameter.
The syntax is like a constructor with no parameter with an additional `~` in front of the name.
%% Cell type:code id: tags:
``` C++17
#include <limits> // for std::numeric_limits
struct Array
{
Array(int unique_id, std::size_t array_size);
~Array(); // Destructor!
double* array_ = nullptr;
const int unique_id_ = std::numeric_limits<int>::min(); // to provide absurd value if not initialized
// That can't happen here, but might if a constructor
// was added later on.
};
```
%% Cell type:code id: tags:
``` C++17
Array::Array(int unique_id, std::size_t array_size)
: unique_id_(unique_id)
{
array_ = new double[array_size];
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
Array::~Array()
{
std::cout << "Memory for array " << unique_id_ << " is properly freed here." << std::endl;
delete[] array_;
}
```
%% Cell type:code id: tags:
``` C++17
{
Array array1(1, 5ul);
{
Array array2(2, 3ul);
{
Array array3(3, 5ul);
}
Array array4(4, 2ul);
}
}
```
%% Output
Memory for array 3 is properly freed here.
Memory for array 4 is properly freed here.
Memory for array 2 is properly freed here.
Memory for array 1 is properly freed here.
%% Cell type:markdown id: tags:
It's important to notice the ordering here: as soon as an object becomes out of scope, it is immediately destroyed; the creation order doesn't matter at all!
We will see a bit [later](/notebooks/5-UsefulConceptsAndSTL/2-RAII.ipynb) how to take advantage of this behaviour to write programs that do not leak memory.
### Default destructor
If not specified, C++ implicitly defines a destructor with an empty body. Personally I like even in this case to make it explicit, which is done the same way as for a constructor from C++11 onward:
%% Cell type:code id: tags:
``` C++17
struct MyClass
{
MyClass() = default; // explicit default constructor
~MyClass() = default; // explicit default destructor
}
```
%% Cell type:markdown id: tags:
This is however a matter of personal taste; see for instance [this post from FluentCpp](https://www.fluentcpp.com/2019/04/23/the-rule-of-zero-zero-constructor-zero-calorie) for the opposite advice of not defining explicitly default constructor / destructor if you don't have to.
%% Cell type:markdown id: tags:
# References
[<a id="cit-Meyers2001" href="#call-Meyers2001">Meyers2001</a>] Scott Meyers, ``_Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library_'', 2001.
[<a id="cit-Meyers2015" href="#call-Meyers2015">Meyers2015</a>] Scott Meyers, ``_Effective modern C++: 42 specific ways to improve your use of C++11
and C++14_'', 2015. [online](http://www.worldcat.org/oclc/890021237)
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2019_
_This notebook is an adaptation of a lecture prepared and redacted by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been redacted by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
%% Cell type:markdown id: tags:
# [Getting started in C++](/) - [Object programming](/notebooks/2-ObjectProgramming/0-main.ipynb) - [Encapsulation](/notebooks/2-ObjectProgramming/4-encapsulation.ipynb)
%% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Public-and-private" data-toc-modified-id="Public-and-private-1">Public and private</a></span></li><li><span><a href="#Struct-and-class" data-toc-modified-id="Struct-and-class-2">Struct and class</a></span></li><li><span><a href="#Why-encapsulation?" data-toc-modified-id="Why-encapsulation?-3">Why encapsulation?</a></span></li><li><span><a href="#Good-practice:-data-attributes-should-be-private" data-toc-modified-id="Good-practice:-data-attributes-should-be-private-4">Good practice: data attributes should be private</a></span></li><li><span><a href="#Good-practices:-the-default-status-of-a-method-should-be-private-and-you-should-use-mutators-for-quantities-that-might-vary" data-toc-modified-id="Good-practices:-the-default-status-of-a-method-should-be-private-and-you-should-use-mutators-for-quantities-that-might-vary-5">Good practices: the default status of a method should be private and you should use mutators for quantities that might vary</a></span></li><li><span><a href="#Friendship" data-toc-modified-id="Friendship-6">Friendship</a></span></li></ul></div>
%% Cell type:markdown id: tags:
## Public and private
We've now played quite a bit with objects now, but we are still using `struct` and not `class` keyword. So it's time to try to define a class:
%% Cell type:code id: tags:
``` C++17
class FirstClass
{
FirstClass();
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
FirstClass::FirstClass()
{
std::cout << "Hello world!" << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
FirstClass object; // COMPILATION ERROR!
}
```
%% Cell type:markdown id: tags:
The compiler informs you that a **private** constructor was called.
In fact, in C++ you we may define the level of access of attributes with three levels (we'll let the third slip until the [chapter about inheritance](/notebooks/2-ObjectProgramming/6-inheritance.ipynb#Protected-status)):
* **private** means only the member functions of the class are entitled to access it (and [friends](/notebooks/2-ObjectProgramming/4-encapsulation.ipynb#Friendship) as we shall see later in this notebook)
* **public** means everyone may access it.
To determine which one is used, `public:` and `private:` may be added in a class declaration:
%% Cell type:code id: tags:
``` C++17
class SecondClass
{
public:
SecondClass(int a);
private:
int a_ = -9999999; // stupid default value.
void SetValue(int a);
public:
void Print() const;
};
```
%% Cell type:code id: tags:
``` C++17
SecondClass::SecondClass(int a)
{
SetValue(a); // Ok: a member function (here the constructor) may call private method
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void SecondClass::Print() const
{
std::cout << "The value is " << a_ << std::endl; // Ok: a member function may use private data attribute.
}
```
%% Cell type:code id: tags:
``` C++17
void SecondClass::SetValue(int a)
{
a_ = a; // Ok: a member function may use private data attribute.
}
```
%% Cell type:code id: tags:
``` C++17
{
SecondClass object(5);
object.Print(); // Ok: public method
}
```
%% Output
The value is 5
%% Cell type:code id: tags:
``` C++17
{
SecondClass object(5);
object.SetValue(7); // COMPILATION ERROR: trying to call publicly a private method
}
```
%% Output
input_line_13:4:12: error: 'SetValue' is a private member of 'SecondClass'
object.SetValue(7); // COMPILATION ERROR: trying to call publicly a private method
 ^
input_line_7:11:14: note: declared private here
void SetValue(int a);
 ^

Interpreter Error:
%% Cell type:markdown id: tags:
As you may see on our example, there might be as many public and private sections as you wish, and their ordering doesn't matter (often coding standards recommend such an ordering, saying for instance to put public interface first, but the langage itself does not care in the least).
One side note for those accustomed to other languages: C++ is really hell bent about privacy status. It is not a gentleman's agreement as in Python where the `_` preffix is an indication an attribute should not be used publicly but a user may supersede the choice anyway; in C++ you can't call directly a private method of a class
without modifying the class interface yourself - which is ill-advised, especially if we're talking about code from a third-party library.
## Struct and class
The difference in a (C++) `struct` and a `class` is in fact thin-veiled:
* A `struct` assumes implicitly attributes are public if nothing is specified.
* A `class` assumes implicitly attributes are private if nothing is specified.
I would advise personally to use classes and specify explicitly the sections (as we shall see very soon it is advised to get at least some private parts) but you now understand why we stick with structs in the former chapters: it allowed not to meddle with public/private concerns.
## Why encapsulation?
So far we've described the mechanism, but not provided much insight on why such a hurdle was introduced in the first place. Let's see a concrete example in which encapsulation benefits appear clearly:
%% Cell type:code id: tags:
``` C++17
struct Rectangle
{
Rectangle(double length, double widgth);
Rectangle(double length, double width);
double length_;
double width_;
double area_;
void Print() const;
};
```
%% Cell type:code id: tags:
``` C++17
Rectangle::Rectangle(double length, double width)
: length_(length),
width_(width),
area_(length * width)
{ }
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void Rectangle::Print() const
{
std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl;
}
```
%% Output
input_line_19:1:17: error: redefinition of 'Print'
void Rectangle::Print() const
 ^
input_line_17:1:17: note: previous definition is here
void Rectangle::Print() const
 ^

Interpreter Error:
%% Cell type:code id: tags:
``` C++17
{
Rectangle rect(5., 4.);
rect.Print(); // OK
rect.length_ = 23.;
rect.Print(); // Not so much...
}
```
%% Output
My rectangle is 5 x 4 so its area is 20
My rectangle is 23 x 4 so its area is 20
%% Cell type:markdown id: tags:
Encapsulation may protect from that:
%% Cell type:code id: tags:
``` C++17
class MoreSecureRectangle
{
public:
MoreSecureRectangle(double length, double widgth);
void Print() const;
private:
double length_;
double width_;
double area_;
};
```
%% Cell type:code id: tags:
``` C++17
MoreSecureRectangle::MoreSecureRectangle(double length, double width)
: length_(length),
width_(width),
area_(length * width)
{ }
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void MoreSecureRectangle::Print() const
{
std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
MoreSecureRectangle rect(5., 4.);
rect.Print(); // OK
rect.length_ = 0.; // can't do that!
}
```
%% Output
input_line_25:5:10: error: 'length_' is a private member of 'MoreSecureRectangle'
rect.length_ = 0.; // can't do that!
 ^
input_line_21:9:16: note: declared private here
double length_;
 ^

Interpreter Error:
%% Cell type:markdown id: tags:
Of course, we have lost functionality here... If we want to add the functionality to change the values, we need more member functions:
%% Cell type:code id: tags:
``` C++17
class Rectangle3
{
public:
Rectangle3(double length, double widgth);
void Print() const;
void SetLength(double x);
void SetWidth(double x);
private:
double length_;
double width_;
double area_;
};
```
%% Cell type:code id: tags:
``` C++17
Rectangle3::Rectangle3(double length, double width)
: length_(length),
width_(width),
area_(length * width)
{ }
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void Rectangle3::Print() const
{
std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
void Rectangle3::SetLength(double x)
{
length_ = x;
area_ = length_ * width_;
}
```
%% Cell type:code id: tags:
``` C++17
void Rectangle3::SetWidth(double x)
{
width_ = x;
area_ = length_ * width_;
}
```
%% Cell type:code id: tags:
``` C++17
{
Rectangle3 rect(5., 4.);
rect.Print(); // OK
rect.SetLength(23.);
rect.Print(); // OK
}
```
%% Output
My rectangle is 5 x 4 so its area is 20
My rectangle is 23 x 4 so its area is 92
%% Cell type:markdown id: tags:
It should be noticed the class above is safer, but not very good nonetheless:
* The computation of the area is written in three different places: constructor, `SetLength()` and `SetWidth()`... It would be better to get a private `ComputeArea()` method that does this task (granted here for this example it might be overkill, but in real cases the operation might be much more than a mere multiplication...)
* The idea to store the `area_` is not that right here: a public method `GetArea()` or `ComputeArea()` or whatever you want to call it would be preferable here. Of course in a more complex problem it might not: it depends whether the computation is costly or not (here: not) and how often you need to use the value in average before recomputing it.
%% Cell type:markdown id: tags:
## Good practice: data attributes should be private
In the example above, we saw that the publicly data attribute could lead to an inconsistent state within the object.
It is often advised (see for instance item 22 of \cite{Meyers2005}) to make all data attributes private, with dedicated methods to access and eventually modify those data. This approach enables fine-tuning of access: you may define whether a given **accessor** or **mutator** should be public or private.
%% Cell type:code id: tags:
``` C++17
class Rectangle4
{
public:
Rectangle4(double length, double width);
// Mutators
void SetLength(double x);
void SetWidth(double x);
// Accessors
double GetLength() const;
double GetWidth() const;
private:
double length_ = -1.20; // a stupid value which at least is deterministically known...
double width_ = -1.20;
double length_ = -1.e20; // a stupid value which at least is deterministically known...
double width_ = -1.e20;
};
```
%% Cell type:code id: tags:
``` C++17
Rectangle4:: Rectangle4(double length, double width)
: length_(length),
width_(width)
{ }
```
%% Cell type:code id: tags:
``` C++17
void Rectangle4::SetLength(double x)
{
length_ = x;
}
```
%% Cell type:code id: tags:
``` C++17
void Rectangle4::SetWidth(double x)
{
width_ = x;
}
```
%% Cell type:code id: tags:
``` C++17
double Rectangle4::GetLength() const
{
return length_;
}
```
%% Cell type:code id: tags:
``` C++17
double Rectangle4::GetWidth() const
{
return width_;
}
```
%% Cell type:code id: tags:
``` C++17
double ComputeArea(const Rectangle4& r) // free function
{
return r.GetLength() * r.GetWidth(); // ok: public methods! And no risk to inadvertably change the values here.
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
Rectangle4 rect(4., 6.5);
std::cout << "Area = " << ComputeArea(rect) << std::endl;
rect.SetWidth(5.);
std::cout << "Area = " << ComputeArea(rect) << std::endl;
}
```
%% Output
Area = 26
Area = 20
%% Cell type:markdown id: tags:
## Good practices: the default status of a method should be private and you should use mutators for quantities that might vary
In our `Rectangle4` example, all mutators and accessors were public. It is clearly what is intended here so it's fine, but as a rule you should really put in the public area what is intended to be usable by an end-user of your class. Let's spin another variation of our `Rectangle` class to illustrate this:
%% Cell type:code id: tags:
``` C++17
class Rectangle5
{
public:
Rectangle5(double length, double width);
// Mutators
void SetLength(double x);
void SetWidth(double x);
// Accessor
double GetArea() const; // others accessors are not defined to limit steps to define the class
private:
// Mutator for the area
// Prototype is not bright (parameters are clearly intended to be the data attributes
// and a prototype with no parameters would be much wiser!)
// but is useful to make my point below.
void SetArea(double length, double width);
private:
double area_ = -1.e20; // a stupid value which at least is deterministic...
double length_ = -1.20;
double width_ = -1.20;
};
```
%% Cell type:code id: tags:
``` C++17
void Rectangle5::SetArea(double length, double width)
{
area_ = width * length;
}
```
%% Cell type:code id: tags:
``` C++17
Rectangle5:: Rectangle5(double length, double width)
: length_(length),
width_(width)
{
SetArea(width_, length_);
}
```
%% Cell type:code id: tags:
``` C++17
double Rectangle5::GetArea() const
{
return area_;
}
```
%% Cell type:code id: tags:
``` C++17
void Rectangle5::SetLength(double x)
{
length_ = x;
SetArea(width_, length_);
}
```
%% Cell type:code id: tags:
``` C++17
void Rectangle5::SetWidth(double x)
{
width_ = x;
SetArea(width_, length_);
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void Print5(const Rectangle5& r)
{
std::cout << "Area is " << r.GetArea() << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
Rectangle5 rect(3., 4.);
Print5(rect);
rect.SetLength(8.);
Print5(rect);
rect.SetWidth(2.);
Print5(rect);
}
```
%% Output
Area is 12
Area is 32
Area is 16
%% Cell type:markdown id: tags:
It's clear that `SetArea()` has no business being called publicly: this member function is introduced here to update the area each time a dimension has changed, but it is assumed to be called with very specific arguments (the data attributes).
A end-user of the class doesn't in fact even need to know there is such a method: for his purpose being able to change one dimension and to get the correct area is all that matters for him.
This rather dumb example illustrates the interest of a mutator: when `SetWidth()` or `SetLength()` are called, the value is assigned **and** another operation is also performed. If when extending a class you need another operations you didn't think of in the first place (imagine for instance you need for some reason to know how many times the length was modified) you have just to modify the code in one place: the definition of the mutator (plus defining a new data attribute to store this quantity and initializing it properly). If you didn't use a mutator, you would end-up search the code for all the locations the data attributes is modified and pray you didn't miss one...
%% Cell type:markdown id: tags:
## Friendship
Sometimes, you may have a need to open access to the private part of a class for a very specific other class or function. You may in this case use the keyword **friend** in the class declaration:
%% Cell type:code id: tags:
``` C++17
#include <limits>
class Vector
{
public:
Vector(double x, double y, double z);
friend double norm(const Vector&);
friend class PrintVector; // In C++11 `class` became optional here.
private:
double x_ = std::numeric_limits<double>::min();
double y_ = std::numeric_limits<double>::min();
double z_ = std::numeric_limits<double>::min();
};
```
%% Cell type:code id: tags:
``` C++17
Vector::Vector(double x, double y, double z)
: x_(x),
y_(y),
z_(z)
{ }
```
%% Cell type:code id: tags:
``` C++17
#include <cmath>
double norm(const Vector& v)
{
return std::sqrt(v.x_ * v.x_ + v.y_ * v.y_ + v.z_ * v.z_); // OK!
}
```
%% Cell type:code id: tags:
``` C++17
class PrintVector // not the cleverest class we could write...
{
public:
PrintVector(const Vector& v);
void Print() const;
private:
const Vector& vector_;
};
```
%% Cell type:code id: tags:
``` C++17
PrintVector::PrintVector(const Vector& v)
: vector_(v)
{}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void PrintVector::Print() const
{
std::cout << "Content of the vector is (" << vector_.x_ << ", " << vector_.y_
<< ", " << vector_.z_ << ")." << std::endl; // OK because of friendship!
}
```
%% Cell type:markdown id: tags:
Obviously, friendship should be used with parcimony... But it's not that much of a deal-breaker as it may seem:
* The friendship must be defined in the class declaration. It means you can't use it to bypass a class encapsulation without modifying this code directly.
* The friendship is granted to a very specific function or class, and this class must be known when the class is defined. So an ill-intentioned user can't use the function prototype to sneak into your class private parts (in fact [forward declaration](/notebooks/6-InRealEnvironment/2-FileStructure.ipynb#Forward-declaration) is an exception to current statement).
%% Cell type:markdown id: tags:
# References
(<a id="cit-Meyers2005" href="#call-Meyers2005">Meyers, 2005</a>) Scott Meyers, ``_Effective C++: 55 Specific Ways to Improve Your Programs and Designs (3rd Edition)_'', 2005.
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2019_
_This notebook is an adaptation of a lecture prepared and redacted by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been redacted by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment