Exam Solutions

Exercise 1

The following C class is given:

  struct C {
    double m_x;
    float m_y;
    float m_z;
};
  

Exercise 1a

Define four variables without automatic type deduction:

  • c: Instance of C on the stack.
  • r: Reference to c.
  • p: Address of r.
  • q: Address of p.
  C c; // C c(); is not valid. This is a function prototype.
C& r = c;
C* p = &r;
C** q = &p;
  

Exercise 1b

What can be said about the value of p->m_x?

It is not initialized.

Exercise 1c

Initialize m_x to 0.5 using r, m_y to 0.1 using p and m_z to 1 using q.

  r.m_x = 0.5;
p->m_y = 0.1f;
(*q)->m_z = 1.0f; // 1f is not valid. f is only valid for floating point literals.
  

Exercise 1d

Create a new instance of C on the heap. Initialize the attributes to 0.5, 0.1 and 1 respectively. Free allocated memory afterwards.

  C* z = new C{0.5, 0.1f, 1.0f};
delete z;
  

Exercise 1e

Write a function foo() that takes an in/out parameter of type C and sets m_x to the sum of m_y and m_z.

  void foo(C& c) {
    c.m_x = c.m_y + c.m_z;
}
  

Exercise 2

Exercise 2a

Does the following code compile?

  #include <iostream>
auto kehrwert(int x) {
    if (x == 0) {
        return 0.0;
    } else {
        return 1.0 / x;
    }
}
int main() {
    std::cout << kehrwert(5) << std::endl;
}
  

Yes, output is 0.2.

Exercise 2b

Does the following code compile?

  #include <iostream>
#include <cmath>
class Point {
    int x, y;
public:
    double len() const { return sqrt(x*x + y*y); }
};
int main() {
    Point p;
    p.x = 3, p.y = 4;
    std::cout << p.len() << std::endl;
}
  

No, x and y are private.

Exercise 2c

Does the following code compile?

  #include <iostream>
struct Zahl {
    int x;
    void verdopple() const { x *= 2; }
};
int main() {
    Zahl z{5}; z.verdopple();
    std::cout << z.x << std::endl;
}
  

No, verdopple() is not const.

Exercise 2d

Does the following code compile?

  #include <iostream>
int main() {
    constexpr int x = 6;
    const int y = x;
    std::cout << y << std::endl;
}
  

Yes, output is 6.

Exercise 2e

Does the following code compile?

  #include <iostream>
auto potenz(int x, int y) {
    int result = 1;
    for (int i = 0; i < y; i++) result *= x;
    return result;
}
int main() {
    constexpr int p = potenz(4, 3);
    std::cout << p << std::endl;
}
  

No, potenz() is not constexpr.

Exercise 3

Complete the function atoi (ascii to integer).

  bool atoi(const char buf[], int32_t& value) {
    constexpr int32_t max = std::numeric_limits<int32_t>::max();
    constexpr int32_t min = std::numeric_limits<int32_t>::min();
    int64_t val = 0;
    const char* p = buf;
    bool negative = false;
    
    if (!p) return false; // check if p is nullptr
    if (*p == '-') {
        negative = true; p++;
    }
    do {
        if (*p < '0' || *p > '9') return false;
        val = val * 10 + (*p - '0'); // Calculate numeric value by subtracting offset
        if (val > max || -val < min) return false;
        p++;
    } while ((*p) != '\0');

    value = negative ? (int32_t)-val : (int32_t)val;
    return true;
}
  

Exercise 4

Exercise 4a

Comment the following code with its output.

  #include <iostream>
#include <memory>

struct Zahl {
    int m_x;
    Zahl(int x) : m_x(x) {
        std::cout << "create " << m_x << std::endl;
    }
    ~Zahl() {
        std::cout << "delete " << m_x << std::endl;
    }
};

std::unique_ptr<Zahl> factory(int val) {
    return std::make_unique<Zahl>(val);
}

int main() {                                                    // Output:
    std::unique_ptr<Zahl> up = factory(1);                      // create 1
    std::shared_ptr<Zahl> sp = factory(2);                      // create 2
    std::weak_ptr<Zahl> wp = sp;                                // (wp points to same object as sp)
    std::cout << std::boolalpha << wp.expired() << std::endl;   // false
    sp = std::move(up);                                         // delete 2
    std::cout << std::boolalpha << wp.expired() << std::endl;   // true
    auto op = sp;                                               //
    std::cout << op.use_count() << std::endl;                   // 2
    op = factory(3);                                            // delete 1
    std::cout << op.use_count() << std::endl;                   // 1
    sp = op;                                                    // delete 1
}                                                               // delete 3
  

Exercise 4b

Comment the following code with its output.

  #include <iostream>
#include <memory>
#include <string>
#include <vector>

class Node {
    std::string m_id;
    std::vector<std::shared_ptr<Node>> m_v;
public:
    Node(const std::string x) : m_id(x) {}
    ~Node() { std::cout << "delete " << m_id << std::endl; }
    void add(const std::shared_ptr<Node>& sp) { m_v.push_back(sp); }
}

auto create(const std::string& id) {
    return std::make_shared<Node>(id);
}

int main() {
    auto A = create("A"); auto B = create("B"); auto C = create("C");
    auto D = create("D"); auto E = create("E"); auto F = create("F");
    auto G = create("G");
    A->add(F); B->add(F); C->add(E); C->add(C);
    D->Add(C); E->add(D); G->add(E);
} // Output: delete B, A, G, F
  

Exercise 5

The following C++ class is given:

  #include <iostream>
using namespace std;

class Fraction {
    int m_num;
    int m_den;
public:
    Fraction(int a = 0, int b = 1) : m_num(a), m_den(b) {
        cout << "ctor 1" << endl;
    }
    Fraction (int x[]) : Fraction(x[0], x[1]) {
        cout << "ctor 2" << endl;
    }
    Fraction(const Fraction& f) : m_num(f.m_num), m_den(f.m_den) {
        cout << "copy ctor" << endl;
    }
    ~Fraction() {
        cout << "dtor" << *this << endl;
    }
    void flip();
    operator double const {
        cout << "convert" << endl;
        return (double)m_num/m_den;
    }
    Fraction& operator-=(const Fraction& other);
    friend ostream& operator<<(ostream& os, const Fraction& f) {
        return os << f.m_num << '/' << f.m_den;
    }
}

int main() {
    int x[] = {1,5};
    Fraction f1 = x;
    Fraction f2{4,3};
    if ((double)f1 < 0.3) {
        Fraction(f1, f2);
    }
    cout << Fraction(f1) << endl;
    cout << "end" << endl;
}
  

Exercise 5a

What is the output of the main function?

  ctor 1
ctor 2
ctor 1
convert
convert // warning: implicit conversion, loss of precision
convert // warning: implicit conversion, loss of precision
ctor 1
dtor 0/1
copy ctor
1/5
dtor 1/5
end
dtor 4/3
dtor 1/5
  

Exercise 5b

Where would a restrictive compiler complain about loss of precision?

Line 36, where f1 and f2 are implicitly converted to double and then to int.

Exercise 5c

How can the class be modified to prevent Fraction f3(f1, f2) from compiling?

Define conversion operator to double as explicit.

  explicit operator double() const {
    cout << "convert" << endl;
    return (double)m_num/m_den;
}
  

Exercise 5d

Implement the function flip() which flips the numerator and denominator. The implementation must be outside of the class.

  void Fraction::flip() {
    std::swap(m_num, m_den);
}
  

Exercise 5e

Implement a function inverse() which returns a new fraction with flipped numerator and denominator. The implementation must be inside of the class.

  Fraction inverse() const {
    return Fraction(m_den, m_num);
}
  

Exercise 5f

Implement the -= operator. Implementation must be outside of the class.

  Fraction& Fraction::operator-=(const Fraction& other) {
    m_num = m_num * other.m_den - other.m_num * m_den;
    m_den = m_den * other.m_den;
    return *this;
}
  

Exercise 5g

Implement the * operator for scalar multiplication. Implementation must be inside of the class.

  friend Fraction operator*(const Fraction& f, int s) {
    return Fraction(f.m_num * s, f.m_den);
}
  

Exercise 5h

Implement the - operator. Implementation must be inside of the class.

  friend Fraction operator-(const Fraction& lhs, const Fraction& rhs) {
    Fraction diff(lhs);
    diff -= rhs;
    return diff;
}
  

Exercise 5i

Which output is generated by the following code?

  Fraction f1{2,3}, f2{3,4};
Fraction f = f1 - f2 * 4; // f = (f1 - (f2 * 4))
  
  ctor 1
ctor 1
ctor 1
copy ctor
copy ctor
dtor -28/12
dtor 12/4
dtor -28/12
dtor 3/4
dtor 2/3
  

Exercise 6

Given the Fraction class from the previous exercise and the following code:

  struct Point {
    Fraction m_x, m_y;
};
class Polygon {
    size_t m_size;
    std::unique_ptr<Point[]> m_points;

public:
    Polygon(const std::initializer_list<Point>& points)
      : m_size(points.size())
      , m_points(std::make_unique_for_overwrite<Point[]>(m_size)) {
        int i = 0;
        for (auto& p : points) m_points[i++] = p;
    }
};

int main() {
    Point p1{{1,2},{10,3}};
    Point p2{{5,2},{1,3}};
    Point p3{{4,2},{10,2}};
    Point p4{{1,1},{2,3}};

    Polygon pg1{p1,p2,p3,p4};
    Polygon pg2{};
    pg2 = std::move(pg1);
    Polygon pg3(std::move(pg2));
}
  

Exercise 6a

Does the code compile?

Yes, the move constructor and move assignment operator are generated by the compiler.

Exercise 6b

Implement the move constructor for the Polygon class.

  Polygon(Polygon&& other) noexcept
  : m_size(std::exchange(other.m_size, 0))
  , m_points(std::exchange(other.m_points, nullptr)) {}
  

Exercise 6c

Implement the move assignment operator for the Polygon class.

  Polygon& operator=(Polygon&& other) noexcept {
    if (this != other) {
        m_size = std::exchange(other.m_size, 0);
        m_points = std::exchange(other.m_points, nullptr);
    }
    return *this;
}