06 Exam
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
Con 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;
}
NOTE: Why should operators be implemented as global functions instead of member functions? Implementing operators as member functions can lead to unexpected asymmetries.
// If operator- is implemented as member function, the following code will not compile:
Fraction f1{1, 2};
Fraction f2{1, 3};
Fraction f3 = f1 - f2; // f1.operator-(f2)
Fraction f4 = f2 - f1; // f2.operator-(f1)
Fraction f5 = f1 - 5; // f1.operator-(5)
Fraction f6 = 5 - f1; // 5.operator-(f1) is not valid