C++ 2024H1 Assignment-12

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 19

Programming in Modern C++: Assignment Week 12

Total Marks : 25

Partha Pratim Das


Department of Computer Science and Engineering
Indian Institute of Technology Kharagpur, Kharagpur – 721302
partha.p.das@gmail.com

April 4, 2024

Question 1
Which of the following smart pointers has/have shared ownership policy? [MSQ, Marks 2]

a) std::auto ptr (C++03)

b) std::weak ptr (C++11)

c) std::unique ptr(C++11)

d) std::shared ptr(C++11)

Answer: b), d)
Explanation:
std::auto ptr (C++03) and std::unique ptr (C++11) follow exclusive ownership, whereas
std::shared ptr and std::weak ptr (C++11) follow shared ownership.

1
Question 2
Consider the program (in C++11) given below. [MCQ, Marks 2]

#include <iostream>
#include <functional>

int compute(int a, int b, int c, const int& d) {


return (d - (a + b + c));
}
int main() {
using namespace std::placeholders;
int x = 5;
auto fun = std::bind(compute, _3, x, _1, std::cref(x));
x = 10;
std::cout << fun(1, 2, 3);
return 0;
}

What will be the output?

a) 1

b) -4

c) -5

d) -13

Answer: a)
Explanation:
The call f(1, 2, 3) results in binding 1 to 1, 3 to 3, and 2 remains unused. The formal
arguments to the call to print are as print(1, 5, 3, 10), which is evaluated as 1.

2
Question 3
Consider the program (in C++11) given below that squares each element of vector vec and
sums all the squared elements. [MSQ, Marks 2]

#include <iostream>
#include <thread>
#include <vector>
#include <functional>
struct summation {
summation(std::vector<int>& iv, int* sum) : iv_{iv}, sum_{sum} { }
void operator()() {
for(auto& i : iv_) {
i *= i;
*sum_ += i;
}
}
std::vector<int>& iv_;
int* sum_;
};

int main() {
std::vector<int> vec { 1, 2, 3, 4 };
int res = 0;
std::thread t { ______________________ }; //LINE-1
t.join();
for(auto i : vec)
std::cout << i << " ";
std::cout << res << " ";
return 0;
}

Fill in the blank at LINE-1 with appropriate option(s) such that the output becomes 1 4 9
16 30 .

a) summation(vec, res)

b) summation(vec, &res)

c) summation(std::ref(vec), &res)

d) std::bind(summation, std::ref(vec), &res)

Answer: b), c)
Explanation:
The constructor of summation receives vec as pass-by-reference and res as pass-by-address.
At option a), the actual parameter res is not an address. Therefore, it is a wrong option.
At option b), the actual parameter vec is a correct way to perform pass-by-reference (or pass-
by-value) and &res is passed-by-address. Therefore, it is a correct option.
At option c), the actual parameter std::ref(vec) is a pass-by-reference explicitly and &res
is passed-by-address. Therefore, it is a correct option.
At option d), the actual parameter summation of std::bind function is a call to the default
constructor of functor summation, which is not provided. Therefore, it is a wrong option.

3
Question 4
Consider the program (in C++11) given below. [MCQ, Marks 2]

#include <iostream>
class myclass{
public:
void print(){ }
};

template <typename T>


class SmartPointer {
public:
explicit SmartPointer(T* ptr = nullptr) {
std::cout << "SmartPtr::ctor(), ";
ptr_ = ptr;
}
~SmartPointer() { delete (ptr_); }
T& operator*() {
std::cout << "SmartPtr::operator*(), ";
return *ptr_;
}
T* operator->() {
std::cout << "SmartPtr::operator->(), ";
return ptr_;
}
private:
T* ptr_;
};

int main() {
SmartPointer<myclass> ptr(new myclass);
myclass* mc = new myclass;
*ptr = *mc;
ptr->print();
return 0;
}

What will be the output?

a) SmartPtr::ctor(), SmartPtr::operator*(), SmartPtr::operator*(),

b) SmartPtr::ctor(), SmartPtr::operator*(), SmartPtr::operator->(),

c) SmartPtr::ctor(), SmartPtr::operator*(), SmartPtr::operator*(),


SmartPtr::operator->(),

d) SmartPtr::ctor(), SmartPtr::operator->(), SmartPtr::operator->(),

Answer: b)
Explanation:
The statement SmartPointer<myclass> ptr(new myclass); calls the constructor of myclass
first and then calls the constructor of SmartPointer, which print SmartPtr::ctor(),.
The statement *ptr = *mc; calls the function operator*() from SmartPointer, which prints
SmartPtr::operator*(),.

4
Finally, The statement ptr->print(); calls the function operator->() from SmartPointer,
which prints SmartPtr::operator->(),.

5
Question 5
Consider the following code segment (in C++11). [MCQ, Marks 2]

#include<iostream>
#include <memory>

void fun(std::shared_ptr<char> p){


std::shared_ptr<char> tp = p;
std::cout << "rc = " << tp.use_count() << " ";
}

int main(){
std::shared_ptr<char> p1(new char(’a’));
{
std::shared_ptr<char> p2(p1);
std::cout << "rc = " << p1.use_count() << " ";
}
std::shared_ptr<char> p3 = p1;
std::cout << "rc = " << p1.use_count() << " ";
fun(p1);
std::cout << "rc = " << p1.use_count() << " ";
p3.reset(new char(’A’));
std::cout << "rc = " << p1.use_count() << " ";
return 0;
}

What will be the output?

a) rc = 2 rc = 3 rc = 4 rc = 4 rc = 3

b) rc = 2 rc = 3 rc = 4 rc = 3 rc = 2

c) rc = 2 rc = 2 rc = 4 rc = 2 rc = 1

d) rc = 2 rc = 2 rc = 3 rc = 2 rc = 1

Answer: c)
Explanation:
The code is explained in the comment:

#include<iostream>
#include <memory>

void fun(std::shared_ptr<char> p){ //rc = 3 since copy-by-value


std::shared_ptr<char> tp = p; //rc = 4
std::cout << "rc = " << tp.use_count() << " "; //rc = 4
}

int main(){
std::shared_ptr<char> p1(new char(’a’)); //rc = 1
{
std::shared_ptr<char> p2(p1); //rc = 2
std::cout << "rc = " << p1.use_count() << " "; //rc = 2
} //rc = 1 since the pointer defined

6
std::shared_ptr<char> p3 = p1; //rc = 2
std::cout << "rc = " << p1.use_count() << " "; //rc = 2
fun(p1);
std::cout << "rc = " << p1.use_count() << " "; //rc = 2 since local pointers
//in fun are deleted
p3.reset(new char(’A’)); //rc = 1
std::cout << "rc = " << p1.use_count() << " "; //rc = 1
return 0;
}}

7
Question 6
Consider the code segment (C++11) given below. [MCQ, Marks 2]

#include <iostream>
#include <memory>

void update(std::shared_ptr<char>& p){


p.reset(new char(’B’));
}

int main() {
auto p1 = std::make_shared<char>(’a’);
auto p2 = p1;
std::weak_ptr<char> wp1 = p1;
std::weak_ptr<char> wp2 = p1;
p1.reset(new char(’A’));
if(auto p = wp1.lock())
std::cout << *p << std::endl;
else
std::cout << "wp1 is expired" << std::endl;
update(p2);
if(auto p = wp2.lock())
std::cout << *p << std::endl;
else
std::cout << "wp2 is expired" << std::endl;
return 0;
}

What will be the output?

a) A B

b) wp1 is expired
wp2 is expired

c) A
wp2 is expired

d) a
wp2 is expired

Answer: d)
Explanation:
The weak ptr<char> wp1 points to an object, which initially was referred by shared ptr p1
and p2. Although, p1 is reset, p2 still refer the object. Thus, wp1 also refer to the same object
storing ’a’ and the first output is ’a’.
The call update(p2 passed p2 as pass-by-reference, which is reset within update. Thus, wp2
refers to nothing and the second output is wp2 is expired.

8
Question 7
Consider the code segment (C++11) given below. [MSQ, Marks 2]

#include <iostream>
#include <vector>
#include <thread>

void add(std::vector<int>& v){


for(int i = 0; i < 2; i++)
v.push_back(i);
}

int main(){
std::vector<int> vi;
std::thread t {&add, std::ref(vi)};
vi.push_back(3);
t.join();
for(auto i : vi)
std::cout << i << " ";

return 0;
}

What is/are the possible output?

a) 3 0 1

b) 0 1

c) 0 3

d) 0 3 1

Answer: a), d)
Explanation:
Since the vector will be printed after function add() and main() add all the values to the
vector, the vector must contain all the values 0, 1, 2. However, they can be added in any
possible order. Therefore, option a) and d) are correct.

9
Question 8
Consider the following code segment (in C++11). [MSQ, Marks 2]
#include <iostream>
#include <thread>
#include <mutex>

struct printer{
int no_papers = 0;
};

struct scanner{
int no_papers = 0;
};

std::mutex mtx_ptr;
std::mutex mtx_snr;
static printer pr;
static scanner sc;

void spooler1(int n) {
std::unique_lock<std::mutex> lck1(mtx_ptr);
std::unique_lock<std::mutex> lck2(mtx_snr);
pr.no_papers += n;
sc.no_papers += n;
std::cout << pr.no_papers << ", " << sc.no_papers << std::endl;
}

void spooler2(int n) {
std::unique_lock<std::mutex> lck1(mtx_snr);
std::unique_lock<std::mutex> lck2(mtx_ptr);
pr.no_papers += n;
sc.no_papers += n;
std::cout << pr.no_papers << ", " << sc.no_papers << std::endl;
}

int main(){
std::thread t1{spooler1, 5};
std::thread t2{spooler2, 8};
t1.join();
t2.join();
return 0;
}
Which of the following cannot be TRUE about the output of the above program?
a) It generates output as:
5, 5
13, 13

b) It generates output as:


8, 8
13, 13

10
c) It generates output as:
8, 8
5, 5

d) It results in deadlock

Answer: c)
Explanation:
Since the code in spooler1 and spooler2 execute in a mutual exclusive manner, the output
can be:
5, 5
13, 13
or
8, 8
13, 13
It may also happen that t1 holds lock on mtx ptr, and t2 holds lock on mtx snr. Then, t1
request to lock on mtx snr, and t2 requests lock on mtx ptr. It results in a deadlock.
However, option c) can not be an output.
Intentionally kept as MSQ

11
Question 9
Consider the code segment (C++11) given below. [MSQ, Marks 2]

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx_rs;
struct resource{ int no_of_instances {10}; };

void consume(resource& r, std::string s, int n){


mtx_rs.lock();
if(n > r.no_of_instances){
std::cout << "consumtion by " << s << " : failed" << ", ";
}
else{
r.no_of_instances -= n;
std::cout << "consumtion by " << s;
std::cout << " : instances available = " << r.no_of_instances << ", ";
}
mtx_rs.unlock();
}

int main(){
resource r;
std::thread t1 {consume, std::ref(r), "X", 3};
std::thread t2 {consume, std::ref(r), "Y", 4};
std::thread t3 {consume, std::ref(r), "Z", 5};
t1.join(); t2.join(); t3.join();
return 0;
}}

What is/are the output/s that is/are not possible? (think logically)

a) consumtion by X : instances available = 7


consumtion by Z : instances available = 2
consumtion by Y : failed

b) consumtion by Z : failed
consumtion by X : instances available = 2
consumtion by Y : failed

c) consumtion by Z : instances available = 5


consumtion by X : instances available = 2
consumtion by Y : failed

d) consumtion by Z : instances available = 5


consumtion by X : instances available = 2
consumtion by Y : instances available = 1

Answer: b), d)
Explanation: Since the entire code of consume is executed after locking mutex, the code is
executed in a mutual exclusive manner. Thus, option b) and d) cannot occur.

12
Programming Questions

Question 1
Consider the following program (in C++14).
• Fill in the blank at LINE-1 with an appropriate definition of mutex mtx rs.

• Fill in the blanks at LINE-2 and LINE-3 with appropriate statements such that all the
modification to temp and nos of instances happen in a mutual exclusive manner.
The program must satisfy the sample input and output. Marks: 3
#include <iostream>
#include <thread>
#include <functional>
#include <chrono>
#include <mutex>
_______________________________; //LINE-1

class resource{
public:
resource() : nos_of_instances_{0} {}
void produce(int n){
_______________________________________; //LINE-2
temp_ = n;
int delay = (int)((double)std::rand() / (double)(RAND_MAX)* 10);
std::this_thread::sleep_for(std::chrono::milliseconds(delay));
nos_of_instances_ += temp_;
}
void consume(int n){
______________________________________; //LINE-3
temp_ = n;
int delay = (int)((double)std::rand() / (double)(RAND_MAX)* 20);
std::this_thread::sleep_for(std::chrono::milliseconds(delay));
nos_of_instances_ -= temp_;
}
int get_nos_of_instances(){
return nos_of_instances_;
}
private:
int nos_of_instances_;
int temp_;
};

void produce(resource& r, int n){


for(int i = 0; i < n; i++){
int delay = (int)((double)std::rand() / (double)(RAND_MAX)* 10);
std::this_thread::sleep_for(std::chrono::milliseconds(delay));
r.produce(i + 1);
}
}

void consume(resource& r, int n){

13
for(int i = n - 1; i >= 0; i--){
int delay = (int)((double)std::rand() / (double)(RAND_MAX)* 15);
std::this_thread::sleep_for(std::chrono::milliseconds(delay));
r.consume(i + 1);
}
}

int main(){
int n, m;
std::cin >> n;
std::cin >> m;
resource r;
std::thread t1{ std::bind(produce, std::ref(r), n) };
std::thread t2{ std::bind(consume, std::ref(r), m) };

t1.join();
t2.join();
std::cout << r.get_nos_of_instances();
return 0;
}

Public 1
Input: 200 200
Output: 0

Public 2
Input: 200 100
Output: 15050

Public 3
Input: 200 300
Output: -25050

Private
Input: 300 300
Output: 0

Answer:
LINE-1: std::mutex mtx rs
LINE-2: std::unique lock<std::mutex> lck(mtx rs)
LINE-3: std::unique lock<std::mutex> lck(mtx rs)
Explanation:
At LINE-1, mutex can be defined as:
std::mutex mtx rs
At LINE-2 and LINE-3 we can use the following statement:
std::unique lock<std::mutex> lck(mtx rs) such that all the modification to temp and
nos of instances happen in a mutual exclusive manner.

14
Question 2
Consider the following program (in C++11) that computes nth prime.
• Fill in the blank at LINE-1 with appropriate header to overload function operator.
• Fill in the blank at LINE-2 to invoke the functor prime numbers asynchronously.
• Fill in the blank at LINE-3 to receive the output from functor prime numbers.
The program must satisfy the sample input and output. Marks: 3
#include <iostream>
#include <future>
#include <cmath>

struct prime_numbers{
const long long n_;
prime_numbers(const long long& n) : n_(n) { }
bool is_prime(long long m){
if (m <= 1)
return false;
for (int i = 2; i <= sqrt(m); i++)
if (m % i == 0)
return false;

return true;
}
______________________________________ { //LINE-1
long long num = 2;
for(int i = 0; i < n_; num++) {
if (is_prime(num)) {
++i;
}
}
return num - 1;
}
};

long long get_nth_prime(int n){


auto a = ______________________________ ; //LINE-2
return ___________________; //LINE-3
}

int main() {
int n;
std::cin >> n;
std::cout << get_nth_prime(n);
return 0;
}

Public 1
Input: 150
Output: 863

15
Public 2
Input: 200
Output: 1223

Private
Input: 1000
Output: 7919

Answer:
LINE-1: long long operator()()
LINE-2: std::async(prime numbers(n))
LINE-3: a.get()
Explanation:
The function header at LINE-1 to overload the function operator can be: long long operator()()
At LINE-2 the asynchronous call to the functor NthPrime() can be made as:
auto a = std::async(prime numbers(n));
At LINE-3 can use the statement: a.get() to receive the result of functor prime numbers.

16
Question 3
Consider the following program in C++11 to represent a doubly linked list, which allows adding
items at the front of the list. Complete the as per the instructions given below:

• Fill in the blank at LINE-1 with appropriate statement so that class node declares dlist
as its friend.

• Fill in the blank at LINE-2 with appropriate statements to traverse the list in forward
direction,

• Fill in the blank at LINE-3 with appropriate statements to traverse the list in backward
direction,

such that it will satisfy the given test cases. Marks: 3

#include<iostream>
#include<memory>

class node;
class dlist{
public:
dlist() = default;
void insert(const int& item);
void forward_traverse();
void backward_traverse();
private:
std::shared_ptr<node> first = nullptr;
std::shared_ptr<node> last = nullptr;
};

class node{
public:
node() = delete;
node(int info) : info_{info}, next{nullptr} {}
_________________________________; //LINE-1
private:
int info_;
std::shared_ptr<node> next;
std::weak_ptr<node> prev;
};
void dlist::forward_traverse(){
for(____________________________________) //LINE-2
std::cout << t->info_ << " ";
}
void dlist::backward_traverse(){
for(____________________________________) //LINE-3
std::cout << p->info_ << " ";
}
void dlist::insert(const int& item){
std::shared_ptr<node> n = std::make_shared<node>(item);
if(first == nullptr){
first = n;
last = first;

17
}
else{
n->next = first;
first->prev = n;
first = n;
}
}

int main(){
dlist dl;
int n, m;
std::cin >> n;
for(int i = 0; i < n; i++){
std::cin >> m;
dl.insert(m);
}
dl.forward_traverse();
std::cout << std::endl;
dl.backward_traverse();
return 0;
}

Public 1
Input:
4
10 20 30 40
Output:
40 30 20 10
10 20 30 40

Public 2
Input:
5
1 2 3 4 5
Output:
5 4 3 2 1
1 2 3 4 5

Private
Input:
6
10 9 8 7 6 5
Output:
5 6 7 8 9 10
10 9 8 7 6 5

Answer:
LINE-1: friend dlist
LINE-2: std::shared ptr<node> t = first; t != nullptr; t = t->next
LINE-3: std::weak ptr<node> t = last; auto p = t.lock(); t = p->prev

18
Explanation:
At LINE-1, we can fill in the blank with friend dlist so that dlist becomes friend of node.
The given program uses smart pointers instead of raw pointers to avoid explicit memory
management. In case of raw pointers, if the allocated memories are not freed as required,
it can result in memory leakage.
Furthermore, in the node class if next and prev both are shared ptr<node> type, it will cause
circular references, which also results in memory leakage. Thus, to avoid the problem of circular
references, next is declared as of type shared ptr<node> and prev as of type weak ptr<node>.
Since in class node, next is a shared ptr, the for loop for forward traversal must be:
for(std::shared ptr<node> t = first; t != nullptr; t = t->next)
Since in class node, prev is a weak ptr, the for loop for reverse traversal must be:
for(std::weak ptr<node> t = last; auto p = t.lock(); t = p->prev)
Note that last is a shared ptr while prev is a weak ptr. So we need to devise a way to
navigate between the two. Recall that weak ptr cannot be used to access the pointee. So
we first get weak ptr t from last. Now for access, we get a shared ptr p by locking the
weak ptr t. This will be used in the loop body. Finally, to progress backward, we get p->prev
and keep in the weak ptr t.

19

You might also like