Institute of Computer Science
  1. Courses
  2. 2022/23 spring
  3. Programming in C++_ENG (MTAT.03.158)
ET
Log in

Programming in C++_ENG 2022/23 spring

  • Home
  • Labs
    • Submission of solutions
    • Results
  • Examination times
  • Guides
  • References

Programming in C++

Lab 8 (extension lab): Bits 'n bobs

This lab contains some additional features of C++. Many of them are not available in all C++ Versions.

auto/decltype

The auto keyword can be used instead of a type, which will be found by the compiler. Notice this is happining at compile time, C++ is always statically typed. It can be helpful for example with iterators:

#include <bits/stdc++.h>
using namespace std;

int main()
{
    // Create a set of strings
    set<string> st;
    st.insert({ "geeks", "for", "geeks", "org" });

    // 'it' evaluates to iterator to set of string
    // type automatically
    for (auto it = st.begin(); it != st.end(); it++)
        cout << *it << " ";

    return 0;
}

Excessive use of auto can make the code hard to read, though.

decltype can be used to derive a type from a variable:

#include <bits/stdc++.h>
using namespace std;

// Driver Code
int main()
{
    int x = 5;

    // j will be of type int : data type of x
    decltype(x) j = x + 5;

    cout << j;

    return 0;
}

Again, this is resolved at compile-time.

Since C++ 14, auto can also be used as a return type of functions (it was possible before, but required extra tricks). The function must return a single type only, though (remeber this is compile time resolution).

typeid

In contrast, typeid can find the type at runtime. Remember polymorphism from last session - how can we find out, what is "really" in a pointer?

#include <iostream>
#include <typeinfo>
using namespace std;

class B
{
    public:

    // Virtual function
    virtual void f() {
        cout << "The base class function is called.\n";
    }
};

class D: public B
{
    public:
    void f() {
        cout << "The derived class function is called.\n";
    }
};

int main()
{
    B base;
    D derived;

    B *basePtr = &base;
    cout << typeid(*basePtr).name() << endl;
    basePtr->f();

    basePtr = &derived;
    cout << typeid(*basePtr).name() << endl;
    basePtr->f();

    return 0;
}

That makes it possible to write code which only calls a method if the object is of a certain type (but notice the compiler dependency of the name).

File handling

C++ can handle files and read/write from/to files. The most relevant classes for that are:

  • ofstream: Stream class to write on files
  • ifstream: Stream class to read from files
  • fstream: Stream class to both read and write from/to files.

cin and cout, which represent standard input and output, inherit from the same classes as ofstream/ifstream do.

An example for reading and writing could look like this:

/* File Handling with C++ using ifstream & ofstream class object*/
/* To write the Content in File*/
/* Then to read the content of file*/
#include <iostream>

/* fstream header file for ifstream, ofstream,
  fstream classes */
#include <fstream>

using namespace std;

// Driver Code
int main()
{
    // Creation of ofstream class object
    ofstream fout;

    string line;

    // by default ios::out mode, automatically deletes
    // the content of file. To append the content, open in ios:app
    // fout.open("sample.txt", ios::app)
    fout.open("sample.txt");

    // Execute a loop If file successfully opened
    while (fout) {

        // Read a Line from standard input
        getline(cin, line);

        // Press -1 to exit
        if (line == "-1")
            break;

        // Write line in file
        fout << line << endl;
    }

    // Close the File
    fout.close();

    // Creation of ifstream class object to read the file
    ifstream fin;

    // by default open mode = ios::in mode
    fin.open("sample.txt");

    // Execute a loop until EOF (End of File)
    while (getline(fin, line)) {

        // Print line (read from file) in Console
        cout << line << endl;
    }

    // Close the file
    fin.close();

    return 0;
}

We use the open method here. Alternatively, there are constructors which accept a file name and open the file. File paths are relative or absolute. Notice they must comply with platform-dependant naming. The << and >> operators work just like in cout/cin. getline() reads a line, there is also get(), which reads a character. close() is important since the streams are buffered. There are options which can be passed to the open operation. When writing, the file can be opened for appending (app) or truncating (trunc), deleting previous content.

It is also possible to read/write binary data (binary). Notice that the strings we used are pure ASCII strings. In particular that means that characters are 7 bits and written as bytes to the files. If a file is opened as binary, it is possible to control all 8 bits. Of course once a file is written, it is possible to consider it as either binary or text (it is all bits after all), but this can have enexptected effects.

Unicode

C++ can handle Unicode, but it needs special handling as strings as well as files:

#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main() {

   // Create some strings with Unicode characters
   wstring ws1 = L"Infinity: \u221E";
   wstring ws2 = L"Euro: _";

   wchar_t w[] = L"Infinity: \u221E";

   wofstream out("tmp\\unicode.txt");
   out << ws2 << endl;
   wcout << ws2 << endl;
}

That should work, but of course it leaves the question open which encoding is chosen. Unfortunately, this seems to depend on implementation and is not clearly defined. Looks like utf-8 is used for me. (yes, langauges like Java have a better way to handle things here.)

Serialization

By default, C++ has no built-in serialization (serialization means reading/writing whole objects to/from disk). There are lots of methods given for it, but most/all of them have limitations (no pointers in object etc.). A pretty good overview is https://isocpp.org/wiki/faq/serialization. Boost seems to be a good library.

Enums

C++ has an enum data type, which can be declared as follows:

enum myenum{value1, value2, value3};

It can then be used as:

myenum e;
e=value3;
cout<<e;

The output here should be 2. This is because internally the enum is converted to integer constants. This also means that a value cannot show up in two enums, and values from enums cannot be used as variable names. Finally, type safety is a problem.

In C++ 11, enum classes solve these problems:

enum class Color { Red, Green, Blue };
Color x = Color::Green;

Modules

We have seen that historically C++ programs are divided into translation units (cpp files, strictly speaking after preprocessing). Includes are then used to make those available somewhere else. In newer C++, modules can take that role. Note this is available from C++ 20, and your compiler may not have it.

Modules have names. They consist of the usual symbols (i. e. letters, numbers, a few special characters) and dots. Dots can serve to divide module names, something like language.finno_ugric.estonian, but this does not involve a hierarchy like Java packages. So language.finno_ugric is a different module, just like mymodule is a different module.

A module is made up of one or more source code files compiled into a binary file. The binary file describes all the exported types, functions, and templates in the module. When a source file imports a module, the compiler reads in the binary file that contains the contents of the module. Reading the binary file is much faster than processing a header file. Also, the binary file is reused by the compiler every time the module is imported, saving even more time. Because a module is built once rather than every time it's imported, build time can be reduced, sometimes dramatically. Compared to header files, modules enforce stricter rules on name clashes.

For a module, we need a module interface unit, which has export module in it:

export module speech;

export const char* get_phrase() {
    return "Hello, world!";
}

We can then use this by importing it:

// main.cpp
import speech;

import <iostream>;

int main() {
    std::cout << get_phrase() << '\n';
}

Modules can be divided into partitions:

// speech.cpp
export module speech;

export import :english;
export import :spanish;

// speech_english.cpp
export module speech:english;

export const char* get_phrase_en() {
    return "Hello, world!";
}

// speech_spanish.cpp
export module speech:spanish;

export const char* get_phrase_es() {
    return "¡Hola Mundo!";
}

// main.cpp
import speech;

import <iostream>;
import <cstdlib>;

int main() {
    if (std::rand() % 2) {
        std::cout << get_phrase_en() << '\n';
    } else {
        std::cout << get_phrase_es() << '\n';
    }
}

Notice the colon : instead of the dot . - a dot does not make a partition, it does not really mean something.

It is also possible to divide interfaces from implementations:

// speech.cpp
export module speech;

import :english;
import :spanish;

export const char* get_phrase_en();
export const char* get_phrase_es();

// speech_english.cpp
module speech:english;

const char* get_phrase_en() {
    return "Hello, world!";
}

// speech_spanish.cpp
module speech:spanish;

const char* get_phrase_es() {
    return "¡Hola Mundo!";
}
  • Institute of Computer Science
  • Faculty of Science and Technology
  • University of Tartu
In case of technical problems or questions write to:

Contact the course organizers with the organizational and course content questions.
The proprietary copyrights of educational materials belong to the University of Tartu. The use of educational materials is permitted for the purposes and under the conditions provided for in the copyright law for the free use of a work. When using educational materials, the user is obligated to give credit to the author of the educational materials.
The use of educational materials for other purposes is allowed only with the prior written consent of the University of Tartu.
Terms of use for the Courses environment