A constructor is a special [[Method]] that is inherently tied to a [[Class]]. It is a type of [[Method]] / Function that *constructs* a new instance of a class. >[!info] Example Used > The “C-String” wrapper that I use as an example in this is a simplified form of how I would actually write this type of class in [[C++]]. See [[CString]] for a full real-world implementation. ### C -> [[C++]] In C, if we were to make a type String that wraps around a `char*`, but conveniently contains the length, it might look something like this: ```c struct String { // Owned pointer to somewhere on the heap // with a null terminated string char* buffer; // Length of the buffer string not including NULL size_t length; }; ``` If we were to make a function to help us create a `String` struct from a normal C `const char*`, it would look something like this: ```c #include <string.h> struct String StringCreate(const char* source) { struct String string; // get length of source string.length = strlen(source); // allocate memory for length of the string + null terminator string.pointer = malloc(sizeof(char) * (string.length + 1)); // copy source into our new memory strcpy(string.pointer, from); // add null termination string.pointer[string.length] = '\0'; return string; } ``` This implementation is essentially a *constructor* in C. Here is what a solution would look like with [[C++]] & Classes. ```cpp class String { public: char* buffer; std::size_t length; String(const char* source) { length = std::strlen(source); buffer = new char[length + 1]; buffer[length] = '\0'; std::strcpy(buffer, source); } } ``` >[!warning] > The following constructor *mostly* works, but with a small exception - see [[#Member Initializer Lists]] after. This does the same thing as the C style - with a notable exception. While in C we would be able to just create a new `String` without using our `StringCreate` function. ```c void print(const String *str) { printf("%s\n", str->buffer); } int main() { String invalid; print(&invalid); // This compiles in C, we can sidestep the 'Constructor' any time we want. return 0; } ``` In this scenario, the lack of `StringCreate` means that the string is not [[Validity|Valid]], *ig.* the [[Invariant]] of `invalid->buffer` pointing to a [[Null Terminated]] string is `false`. In [[C++]] with constructors, the creation of any `String` is tied to the class’s constructor, therefore whenever you create a string you are *required* to go through its constructors, which ensures that it is impossible$^*$ to have any value of type `String` that is invalid ([[The Goal]]). ```cpp void print(const String* str) { std::cout << str->buffer << std::endl; } int main() { String bruh; // compiler error, there is no constructor for String that has no arguments - so you are required to use the one there is. String str = String("bruh"); print(&str); return 0; } ``` Constructors make it *required* for you to call code to initialize your data. ### Calling Constructors There are a plethera of ways to call a constructor, for this example, I will be adding an [[Overload]] of our constructor that takes in no arguments and creates an empty string. ```cpp class String { // ... Previous Code String() { length = 0; buffer = new char[1]; buffer[0] = '\0'; } // ... } ``` With these two constructors, we can initialize a new instance of string in all of the following ways: ```cpp int main() { // Constructor that takes a const char* String str0 = String("bruh"); String str1 = String{"bruh"}; String str2("bruh"); String str3{"bruh"}; String str4 = {"bruh"}; // Default Constructor String str5 = String(); String str6 = String{}; String str7(); String str8{}; String str9 = {}; String str10; // // Using a constructor for a heap-allocated version of the class String *heap0 = new String("bruh"); String *heap1 = new String{"bruh"}; // Heap Allocated default constructor String *heap2 = new String(); String *heap3 = new String{}; String *heap4 = new String; } ``` >[!note] While it may look like `str10` is uninitialized, [[C++]] will actually run the [[#Default Constructor]]. You are *never* allowed to sidestep creating a valid instance without using a constructor. ### Member Initializer Lists In [[C++]], we want this [[The Goal]] to always hold true, which in part means to minimize uninitialized data in order to ensure the [[Validity]] of the values we are working with. Because of this, constructors, in order to give a value for each fields, requires the member initializer list. ```cpp class Foo { int field1, field2; // List after the Foo() : field1(0), field2(0) {} } ``` What this list does is for each field in the list, it assigns the data specified to it. While you may try to do all your initialization in terms of assignments in the constructor body, you are required to initialise fields in the member list in order to ensure that you do not have any uninitialized data. Here is what our modified form of our String constructor would look like: ```cpp class String { // ... String() : buffer(nullptr), length(0) { // Even though we assign it here, if we didnt set // it to nullptr in the member initializer list, then anything before this assignment could access uninitialized data. buffer = new char[1]; } // ... } ``` >[!important] > When making a member initialiser list, the compiler will yell at you if you do not list the fields *in the order in which they were declared*. For the above example, that is `buffer` then `length`. > >The reason for this is because no matter what order you put the fields in the initialiser list, the compiler will *always* assign the fields *in the order in which they are declared.* #### Other Constructors Calls There is another property of member initialiser lists, which is the ability for constructors to call other constructors. ```cpp class String { // ... // Normal Constructor String(const char* source) : buffer(nullptr), length(std::strlen(source)) { buffer = new char[length + 1]; std::strcpy(buffer, source); buffer[length] = '\0'; } // Default Constructor String() : String("") {} // ... } ``` In the [[Default Constructor]] above, it calls another constructor defined in String to create a String from an empty string to make a String with 0 length. Any code you put inside the constructors body (`{}`) will run *after* the constructor you are calling (`String("")`) is run. >[!tip] > The example above was to show an example, even though it differs how one would actually implement this feature. Here is what I would’ve done if I were to make this class for an actual project: >```cpp >class String { > // ... > > // Default Argument > String(const char* source = "") > : buffer(nullptr), length(std::strlen(source)) { > buffer = new char[length + 1]; > std::strcpy(buffer, source); > buffer[length] = '\0'; > } > > // ... >} >``` ## Special forms of Constructors - [[Default Constructor]] - [[Copy Constructor]] - [[Conversion Constructor]] - [[Move Constructor]] - [[Factory Methods]] ## A Note about Internal Implementation If you have a constructor of type $T$ that takes no arguments, you might assume that under the hood the function signature of the constructor would look something like this: $ \huge \begin{align} \text{\color{lightyellow}{T}\color{gray}::\color{lightyellow}T\color{white}() \{ \}} \\ \color{gray}\text{void} \color{white} \to \color{lightyellow}\text{T} \end{align} $ However that isn't how things are done, after your project compiles and all the dust has settled, the function signature of your constructor would actually look something like this: $ \huge \begin{align} \text{\color{lightyellow}{T}\color{gray}::\color{lightyellow}T\color{white}() \{ \}} \\ \color{lightyellow}\text{T}\color{white}*\to \color{gray}\text{void} \end{align} $ >[!example] More accurate StringCreate example from [[C++]] to C >```c >void StringCreate(String* out, const char* source) { > out->length = strlen(source); > out->buffer = malloc(sizeof(char) * (out->length + 1)); > strcpy(out->buffer, source); > out->buffer[out->length] = '\0'; >} > >int main() { > String string; > StringCreate(&string, "What"); > return 0; >} >``` >[!warning] Opinion I, personally, find this very gross. This function signature means that $T$ is an [[Output Parameter]], meaning inside a constructor you have a pointer to an invalid $T$ ([[“This”]]) (conflicts with the [[The Goal]]). > You may think that after you initialise all fields in [[“This”]], it will then point to a valid $T$, however that isn't true. The [[C++]] Object Model states that *a value of a user-defined type $T$ is only [[Validity|valid]] after its constructors ends* (in part due to the creation of $T$’s [[Virtual Table|v-table]]).