My first steps:
- I have enrolled for a Pluralsight course
- I have installed a C++ syntax extension on my beloved editor.
Discovery #1: Classes feel like language extensions
In Go, primitive types are special. Some properties only apply to them; for example, there is no way of defining a behaviour for make(myType)
. Every primitive type brings its own built-in constructor, and custom types inherit the constructor from the primitive type they’re based on. If you want a new instance of your type to be any different than “the zero value for the underlying type”, then you have to declare a very explicit New
function and write a compelling comment advocating its use.
In C++, by defining a constructor you replace the built-in constructor. Declaring a new object of that class will trigger its constructor. While this simple fact may seem obvious to many, it’s not necessarily obvious for a Go programmer like me 😁.
Discovery #2: Memory is deallocated automatically
Contrary to my expectations, it seems like I don’t have to explicitly free the memory when I’m done with a variable in C++. Instead, the deconstructor is called and the corresponding memory is freed automatically, as soon as the variable is out of scope. Sounds like a good idea!
However, I suspect that the future me, naïvely passing pointers around, will eventually get a very vivid idea why reference-counting garbage collection is so popular nowadays.
Discovery #3: no reflection?
For every class, I declare properties and methods (why do they call them “functions”?) upfront in a Person.h
header file. Then I write the function bodies in Person.cpp
. At first sight, it looks redundant. But there’s a bright side: by just reading the .h
file, I know exactly what information the caller function knows about a specific class.
What is this Precompiled header voodoo?
I guess I will eventually find out. In the meantime:
rm src/*.ghc && make build
¯\(ツ)/¯
My first C++ lines
// src/Person.h
#include <string>
class Person {
// Private properties are not reachable from outside the class itself.
// In other terms: only functions attached to this class can see them.
private:
std::string firstname;
std::string lastname;
int arbitrarynumber;
public:
// A function named after the class is the constructor.
// This one takes no arguments.
Person();
// This is the destructor: a function that will be called as soon as
// the object gets out of scope.
~Person();
// This is an alternative constructor: if the object is instantiated
// with this specific function signature, then this constructor will be
// run.
Person(std::string first, std::string last, int arbitrary);
// Apaprently, it is common to provide public getters instead of
// exposing the object properties outside the class.
//
// In this case, we will join firstname and lastname.
std::string getName();
};
// src/Person.cpp
#include <iostream>
#include "Person.h"
// If the Person is declared without arguments, then it's me by default.
//
// One interesting thing here, is that the properties are initialised **before**
// the actual function body. This way, we avoid them being first initialised by the
// compiler, and a second time by us.
Person::Person() :
firstname("Pierre"),
lastname("P"),
arbitrarynumber(34)
{
// Produce some output, to let us know when this constructor is called.
std::cout << "constructing..." << getName() << std::endl;
}
// If two strings and an int are passed on declaration, then this constructor
// is called.
Person::Person(std::string first, std::string last, int arbitrary) :
firstname(first),
lastname(last),
arbitrarynumber(arbitrary)
{
std::cout << "constructing..." << getName() << std::endl;
}
// The destructor. Just to log stuff.
Person::~Person()
{
std::cout << "destructing " << getName() << std::endl;
}
std::string Person::getName()
{
return firstname + " " + lastname;
}
// src/Main.cpp
#include <iostream>
#include "Person.h"
int main()
{
std::string pierreName;
{
// Here we create me and save my name in the variable declared
// in the outer block.
Person pierre;
pierreName = pierre.getName();
}
Person vale("Valentina", "C", 32);
std::string valeName = vale.getName();
std::cout << valeName << std::endl;
std::cout << pierreName << std::endl;
// In my experiments, this value is returned to the shell as the exit
// code.
return 0;
}
# Output of ./a.out
constructing...Pierre P
destructing Pierre P
constructing...Valentina C
Valentina C
Pierre P
destructing Valentina C