"The struct we used previously would work the same in C code (with the exceptions of references: with a C compiler you would have to stick with pointers).\n",
"\n",
"But when Bjarne Stroustrup created the C++, its main idea was to extend these structs into full-fledged **classes** (to the point the working name of his language was *C with classes*...)\n",
"But when Bjarne Stroustrup created the C++, its main idea was to extend these structs into full-fledged **classes** (to the extent that the working name of his language was *C with classes*...)\n",
"\n",
"One of the idea that was missing with original C `struct` was the possibility to add as well member functions:"
]
...
...
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Member functions](./2-Member-functions.ipynb)
%% Cell type:markdown id: tags:
<h1>Table of contents<spanclass="tocSkip"></span></h1>
<divclass="toc"><ulclass="toc-item"><li><span><ahref="#Member-functions"data-toc-modified-id="Member-functions-1">Member functions</a></span></li><li><span><ahref="#The-this-keyword"data-toc-modified-id="The-this-keyword-2">The <code>this</code> keyword</a></span></li><li><span><ahref="#Separating-declaration-and-definition"data-toc-modified-id="Separating-declaration-and-definition-3">Separating declaration and definition</a></span></li><li><span><ahref="#Const-methods"data-toc-modified-id="Const-methods-4">Const methods</a></span><ulclass="toc-item"><li><span><ahref="#mutable-keyword"data-toc-modified-id="mutable-keyword-4.1"><code>mutable</code> keyword</a></span></li></ul></li></ul></div>
%% Cell type:markdown id: tags:
## Member functions
The struct we used previously would work the same in C code (with the exceptions of references: with a C compiler you would have to stick with pointers).
But when Bjarne Stroustrup created the C++, its main idea was to extend these structs into full-fledged **classes** (to the point the working name of his language was *C with classes*...)
But when Bjarne Stroustrup created the C++, its main idea was to extend these structs into full-fledged **classes** (to the extent that the working name of his language was *C with classes*...)
One of the idea that was missing with original C `struct` was the possibility to add as well member functions:
%% Cell type:code id: tags:
``` C++17
#include <cmath>
struct Vector
{
double x;
double y;
double z;
void Init(double x, double y, double z)
{
this->x = x;
this->y = y;
this->z = z;
}
double Norm()
{
return std::sqrt(x * x + y * y + z * z);
}
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
Vector v;
v.Init(5., 6., -4.2);
std::cout << v.Norm() << std::endl;
}
```
%% Cell type:markdown id: tags:
Let's do a bit of taxonomy here:
-`Init()` and `Norm()` are called **member functions** or **methods**. The same remark concerning C++ purist I did for member variables may be applied here.
-**Method** is used in other programming languages, but for some reason Julia creators used this exact term for an entirely different concept. So to put in a nutshell a C++ method is akin to a Python one but not to what Julia calls a method.
-**Attributes** are in fact the data attributes AND the methods. It is however often used only to designate the data attributes.
**WARNING**: In C++ you can't complete a class after the fact as you could for instance in Python. So all the methods and data atttributes have to be declared within the struct brackets here; if you need to add something you will have to recompile the class. This means especially you can't add directly a member function to a class provided by a third party library; we'll see shortly the mechanism you may use instead to do your bidding.
%% Cell type:markdown id: tags:
## The `this` keyword
The `this->` may have puzzled you: it is a keyword to refer to the current object. So when you call `v.Init(...)`, this is an implicit reference to `v`.
In most cases, it might be altogether removed; we have to put it explicitly here solely because we named the `Init` parameters with the same name as the data attribute. If not, we could have avoided to mention it completely.
An usual convention is to suffix data attributes with a `_` (**be careful**, attributes prefixed with a `_` is reserved by the C++ standard); doing so remove the need to the explicit `this`:
%% Cell type:code id: tags:
``` C++17
#include <cmath>
struct Vector2
{
double x_;
double y_;
double z_;
void Init(double x, double y, double z)
{
x_ = x; // no need to `this`here as there is no ambiguity between data attribute name and parameter name
y_ = y;
z_ = z;
}
double Norm()
{
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_);
}
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
Vector2 v;
v.Init(5., 6., -4.2);
std::cout << v.Norm() << std::endl;
}
```
%% Cell type:markdown id: tags:
That is not to say you should forget altogether the `this` keyword: it might be necessary in some contexts (for templates for instance - see [later](../4-Templates/3-Syntax.ipynb)...)
%% Cell type:markdown id: tags:
## Separating declaration and definition
We have defined so far the method directly in the class declaration; which is not very clean. It is acceptable for a very short method as here, but for a more complex class and method it is better to separate explicitly both. In this case you will have:
- On one side, usually in a header file (we'll see the file structure in real C++ code [later on](../6-InRealEnvironment/2-FileStructure.ipynb)):
%% Cell type:code id: tags:
``` C++17
struct Vector3
{
double x_;
double y_;
double z_;
void Init(double x, double y, double z);
double Norm();
};
```
%% Cell type:markdown id: tags:
- On another side the definition, usually in a source file which includes the header file:
%% Cell type:code id: tags:
``` C++17
void Vector3::Init(double x, double y, double z)
{
x_ = x;
y_ = y;
z_ = z;
}
```
%% Cell type:code id: tags:
``` C++17
double Vector3::Norm()
{
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_);
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
Vector3 v;
v.Init(5., 6., -4.2);
std::cout << v.Norm() << std::endl;
}
```
%% Cell type:markdown id: tags:
Please notice the `::` syntax which specifies the class for which the implementation is provided. Also pay attention to the fact the `this` may as well be implicitly used.
%% Cell type:markdown id: tags:
## Const methods
Are we happy here with what we have so far? Unfortunately, not quite...
If we define a simple free function that print the norm of a `Vector3`:
... we see that doesn't compile. So what is happening?
The issue here is that the function `PrintNorm` takes as argument a constant reference, and has to guarantee the underlying object is not modified in the process. A "patch" would be to define it without the const:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void PrintNormNoConst(Vector3& v) // BAD IDEA!
{
std::cout << v.Norm() << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
Vector3 v;
v.Init(5., 6., -4.2);
PrintNormNoConst(v);
}
```
%% Cell type:markdown id: tags:
Why is it such a poor idea? C++ is a compiled language, and this has its (many) pros and (many) cons. One of the advantages is to be able to leverage the compilation to detect at early time something is amiss. Here the compilation error is a good way to see we might be doing something wrong.
The sketchy "patch" I provided would be akin to ignoring the `const` feature almost entirely whenever objects are concerned.
The proper way to solve the issue is in fact quite the opposite: we may specify when writing a method that it is not allowed to modify the state of the object:
%% Cell type:code id: tags:
``` C++17
struct Vector4
{
double x_;
double y_;
double z_;
void Init(double x, double y, double z);
double Norm() const; // notice the additional keyword!
void DontPutConstEverywhere() const;
};
```
%% Cell type:code id: tags:
``` C++17
void Vector4::Init(double x, double y, double z)
{
x_ = x;
y_ = y;
z_ = z;
}
```
%% Cell type:code id: tags:
``` C++17
double Vector4::Norm() const
{
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_);
}
```
%% Cell type:markdown id: tags:
Please notice `const` needs to be specified both on declaration and on definition: if not provided in definition the signature of the method won't match and the compiler will yell.
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void PrintNorm(const Vector4& v)
{
std::cout << v.Norm() << std::endl;
}
{
Vector4 v;
v.Init(5., 6., -4.2);
PrintNorm(v);
}
```
%% Cell type:markdown id: tags:
Obviously, if we try to ignore a `const` keyword, the compiler will also yell (it is and SHOULD BE very good at this!):
%% Cell type:code id: tags:
``` C++17
void Vector4::DontPutConstEverywhere() const
{
x_ = 0.; // ERROR!
}
```
%% Cell type:markdown id: tags:
### `mutable` keyword
Tread with extreme caution here! Sometimes, you might want for a method to be mostly unable to modify the state of the class but you still need to modify one or more attribute. You may in this case use the `mutable` keyword when defining this attribute:
%% Cell type:code id: tags:
``` C++17
struct Vector5
{
double x_;
double y_;
double z_;
mutable unsigned int Nnorm_calls_;
void Init(double x, double y, double z);
double Norm() const;
};
```
%% Cell type:code id: tags:
``` C++17
void Vector5::Init(double x, double y, double z)
{
x_ = x;
y_ = y;
z_ = z;
Nnorm_calls_ = 0u;
}
```
%% Cell type:code id: tags:
``` C++17
double Vector5::Norm() const
{
++Nnorm_calls_;
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_);
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
Vector5 v;
v.Init(5., 6., -4.2);
for (int i = 0; i < 5; ++i)
v.Norm();
std::cout << "Method 'Norm()' was called " << v.Nnorm_calls_ << " times." << std::endl;
}
```
%% Cell type:markdown id: tags:
I must stress again that you should use this in a **last resort**!
For my part, I have used this in only two contexts:
* Using a work variable that would have been costly to reallocate at each call. This variable was always reset and used within the method that calls it and was not used to share a state between methods.
* For mutexes when using shared memory parallelism. This is way out of the scope of this tutorial.
_This notebook is an adaptation of a lecture prepared 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 written by Sébastien Gilles and Vincent Rouvreau (Inria)_