You are on page 1of 33

Hussan Bano

Roll No:22f-8802
Section :3F
Mini Assignment
Question#01
i. Implicit copy constructors :
Implicit copy constructors are automatically generated by the compiler when a
user-defined class does not provide its own copy constructor. These implicitly
generated copy constructors perform a member-wise copy of the source object's
data members to the new object.

Example:
#include <iostream>

using namespace std;

class Example {

public:

int data;

// Implicit copy constructor will be generated by the compiler

};

int main() {
Example obj1;

obj1.data = 42;

Example obj2 = obj1; // Implicit copy constructor is called here

cout << obj2.data << endl;

return 0;

ii.In the context of copy constructors, why is it a requirement that the


parameter of a copy
constructor must be a (const) reference? Explain the rationale behind this design
choice.
Using a (const) reference as the parameter of a copy constructor is a design choice
to avoid unnecessary copies and improve performance. Passing by reference
allows the constructor to work with the original object directly, without creating a
temporary copy. Additionally, using const ensures that the original object remains
unchanged during the copy process.

Example:
class Example {

public:

int data;

Example(const Example& other) {

// Copy constructor logic here


}

};

iii.Discuss the potential consequences and issues that may arise if pass by
value were
allowed for the copy constructor parameter. Provide examples to illustrate
your points.
If pass by value were allowed for the copy constructor parameter, it would lead to a
recursive invocation of the copy constructor, resulting in an infinite loop and a
stack overflow. This happens because creating a copy involves calling the copy
constructor, and passing by value would trigger the copy constructor again, leading
to an endless cycle.

Example:
class Example {
public:
int data;

Example(Example other) { // This would cause an infinite loop


// Copy constructor logic here
}
};

iv.Give the best practices for defining copy constructors.

2. Always use a (const) reference parameter to avoid unnecessary copies.

3. Perform a deep copy of dynamically allocated resources if your class


manages them.

4. Ensure that the copy constructor creates an independent copy of the


object.cpp

Example:
class Example {

public:

int* dataArray;

int size;

Example(const Example& other) {

size = other.size;

dataArray = new int[size];

// Perform deep copy of the data

// ...

~Example() {

delete[] dataArray;

};

v.What is the rule of three which was later expanded to rule of five?
The Rule of Three states that if a class defines or deletes one of the following three
special member functions, then it should define or delete all three:

Destructor

1.Copy constructor

2.Copy assignment operator

The Rule of Five extends this concept to include two additional special member
functions for managing move semantics:

4. Move constructor

Move assignment operator

If a class needs to provide custom implementations for any of these operations, it's
generally a good practice to provide all of them to ensure proper resource
management and avoid unexpected behavior.

vi.Shallow vs deep copy and give examples:

Shallow Copy: Copies the values of the data members, but does not
create new copies of dynamically allocated resources. Both the original and the
copy share the same resources.

class ShallowCopyExample {

public:

int* data;

ShallowCopyExample(const ShallowCopyExample& other) {


data = other.data; // Shallow copy

};

Deep Copy: Creates new copies of dynamically allocated resources to


ensure that the original and the copy are independent of each other.

class DeepCopyExample {

public:

int* data;

DeepCopyExample(const DeepCopyExample& other) {

data = new int(*other.data); // Deep copy

~DeepCopyExample() {

delete data;

};

Question#02
i.Diamond Inheritance Problem:
Explain the Diamond Inheritance Problem
in C++ in the context of function
overriding. Elaborate on
the challenges and ambiguities that may
arise when a class inherits from two
classes that share a
common base class. Discuss potential
solutions or mechanisms in C++ to address
the issues associated
with the Diamond Inheritance Problem.
The Diamond Inheritance Problem is a common issue in object-oriented
programming languages like C++ that support multiple inheritance. It arises when
a class inherits from two classes that share a common base class. This situation
forms a diamond-shaped inheritance hierarchy, leading to challenges and
ambiguities, particularly in the context of function overriding.

Let's consider a simple example to illustrate the Diamond Inheritance Problem:

class Base {

public:

virtual void display() {

cout << "Base class" << endl;


}

};

class Derived1 : public Base {

public:

virtual void display() override {

cout << "Derived1 class" << endl;

};

class Derived2 : public Base {

public:

virtual void display() override {

cout << "Derived2 class" << endl;

};

class DiamondProblem : public Derived1, public Derived2 {

};

int main() {

DiamondProblem diamondObj;
diamondObj.display(); // Ambiguity: Which display function should be called?

return 0;

In the DiamondProblem class, there are two paths to the Base class, one through
Derived1 and another through Derived2. When we try to call the display function
on an instance of DiamondProblem, the compiler faces ambiguity regarding which
version of the display function to invoke.

Challenges and Ambiguities:

Ambiguous Function Call: The compiler cannot determine whether to call the
display function from Derived1 or Derived2, leading to ambiguity.

Redundant Copies: The common base class (Base in this case) may have data
members that are duplicated in the derived class, resulting in redundant copies
and potential inefficiencies.

Potential Solutions and Mechanisms:

Virtual Inheritance:

Use the virtual keyword for the common base class in the derived classes to
ensure a single shared instance of the base class.

class Derived1 : virtual public Base {

// ...
};

class Derived2 : virtual public Base {

// ...

};

Virtual inheritance helps in avoiding redundant copies of the base class and
resolves the ambiguity. However, it may add some complexity to the object layout
and may have a slight performance overhead.

Scope Resolution Operator:

Use the scope resolution operator to explicitly specify which version of the
function to call.

int main() {

DiamondProblem diamondObj;

diamondObj.Derived1::display(); // Explicitly call display from Derived1

return 0;

This approach allows you to specify the path to the desired function, resolving the
ambiguity. However, it requires awareness of the class hierarchy and may not be
the most maintainable solution.
Avoid Multiple Inheritance:

In some cases, it might be reasonable to reconsider the design and avoid multiple
inheritance altogether. This approach simplifies the class hierarchy and eliminates
the Diamond Inheritance Problem.

Composition over Inheritance:

Instead of relying on multiple inheritance, use composition to include


functionality from different classes without the issues associated with the
Diamond Inheritance Problem.

Choosing the appropriate solution depends on the specific requirements and


design considerations of the application. Virtual inheritance is a common and
effective mechanism to address the Diamond Inheritance Problem, but it's
essential to carefully consider the trade-offs and potential impacts on
performance and code complexity.

ii.Function Overriding in Modern C++ Design:

In the context of modern C++ design, discuss how function overriding is employed
to achieve

polymorphic behavior. Highlight the key issues and considerations when designing
complex class

hierarchies that involve function overriding. Provide insights into best practices
and patterns used in

real-world scenarios to mitigate challenges related to function overriding in large-


scale software
projects.

In modern C++ design, function overriding is a fundamental concept that enables


polymorphic behavior through the use of virtual functions and dynamic dispatch.
Here are several key aspects and considerations when employing function
overriding in complex class hierarchies:

1. Use of Virtual Functions:

Declare functions in the base class as virtual to enable dynamic dispatch. This
allows the appropriate derived class's version of the function to be called based
on the actual type of the object at runtime.

class Base {

public:

virtual void performAction() {

// Base class implementation

};

class Derived : public Base {

public:

void performAction() override {

// Derived class implementation

}
};

2. Dynamic Dispatch and Polymorphism:

Leverage dynamic dispatch to achieve polymorphism. Virtual functions enable the


selection of the appropriate function based on the actual type of the object at
runtime.

void executeAction(Base* obj) {

obj->performAction(); // Calls the correct version of performAction dynamically

3. Avoid Slicing:

Be cautious of object slicing, which occurs when a derived class object is assigned
to a base class object. This can result in the loss of derived class-specific
information.

Base baseObj = derivedObj; // Slicing may occur

Instead, use pointers or references to the base class.

4. Designing Interfaces:

Focus on designing clear and concise interfaces in base classes. This helps in
creating a stable foundation for derived classes and facilitates maintainability.

5. Override Keyword:

Use the override keyword when overriding virtual functions. This ensures that you
are indeed overriding a function from the base class, helping to catch potential
mistakes during compilation.

class Derived : public Base {


public:

void performAction() override {

// Correctly overrides performAction from the base class

};

6. Final Keyword:

In scenarios where further derivation is not intended, use the final keyword to
prevent further overriding. This can enhance code safety and prevent
unintentional changes in certain parts of the class hierarchy.

class Base final {

// ...

};

7. Pure Virtual Functions and Abstract Classes:

Design abstract classes with pure virtual functions to create interfaces. This
ensures that derived classes must provide implementations for these functions,
enforcing a common interface across the hierarchy.

class AbstractBase {

public:

virtual void pureVirtualFunction() = 0; // Pure virtual function

};

8. Covariant Return Types:

Utilize covariant return types when appropriate. In C++11 and later, you can use
covariant return types to make the return type of a derived class function more
specific than the one in the base class.

class Base {

public:

virtual Base* clone() const;

};

class Derived : public Base {

public:

virtual Derived* clone() const override;

};

9. Smart Pointers and Ownership:

Use smart pointers (e.g., unique_ptr, shared_ptr) to manage object ownership and
lifetimes. This can help prevent memory leaks and ensure proper resource
management.

unique_ptr<Base> ptr = make_unique<Derived>();

10. Testing and Debugging:

Thoroughly test and debug class hierarchies involving function overriding. Use
tools like dynamic_cast and run-time type information (RTTI) judiciously to ensure
the correct types are being used.

11. Composition Over Inheritance:

Consider using composition over inheritance in situations where complex class


hierarchies may lead to maintenance challenges. Favoring composition can result
in more flexible and maintainable designs.
In real-world scenarios, the use of function overriding is often intertwined with
other design patterns such as the Strategy Pattern, Factory Pattern, and
Composite Pattern to create robust and flexible software architectures. Careful
consideration of design principles, encapsulation, and SOLID principles contributes
to the creation of scalable and maintainable codebases involving polymorphic
behavior.

Question#03
Copy Constructor Decision:

Considerations:

Shallow Copy:

Advantages: Shallow copying is generally more efficient in terms of


performance, as it copies the memory addresses of the elements rather than the
elements themselves.

Consideration: However, it may lead to issues if the original object and the copy
share the same underlying array, as changes in one object would affect the
other.

Deep Copy:
Advantages: Deep copying creates independent copies of the array, preventing
unintended side effects from modifications in one instance affecting others.

Consideration: It can be less efficient in terms of memory and time, especially if


the array is large or contains complex objects.

Combination (Smart Copying):

Advantages: A combination of both approaches can be used in certain scenarios.


For example, use shallow copy for non-complex types and deep copy for
complex types within the array.

Consideration: This approach requires careful management to avoid unintended


consequences and may increase complexity.

Potential Issues:

Shallow Copy Issues:

If a shallow copy is made and the array contains non-trivial objects with
resource ownership (e.g., dynamically allocated memory), modifying one object
might lead to unexpected behavior in others.

Deep Copy Issues:

Deep copying can be resource-intensive, especially for large arrays, leading to


performance issues. It may also require the implementation of custom copy
constructors for complex objects within the array.

Combination Issues:

Managing a combination of shallow and deep copies requires careful handling of


object ownership and can be error-prone. Inconsistent copying strategies may
lead to bugs that are hard to diagnose.

Real-World Use Case:

Scenario:

Consider a scenario where the DynamicArrayManager class is used to manage


an array of objects representing complex data structures, such as images, stored
in memory.

Considerations:

If the array contains simple data structures (e.g., integers, doubles), a shallow
copy might be appropriate for efficiency.

If the array contains complex objects with dynamic memory allocations (e.g.,
images with pixel data), a deep copy may be necessary to prevent unintended
side effects.

Implications:

Choosing a shallow copy in this scenario could lead to unexpected behavior, as


modifications to the original or copied array might affect the integrity of the
complex objects.

Opting for a deep copy ensures independence of instances, preventing


unintended modifications, but may come at the cost of increased memory usage
and computational overhead.

Justification:

Given the scenario of managing complex data structures like images, where
maintaining data integrity is crucial, a deep copy constructor would be more
appropriate. It ensures that each instance of the DynamicArrayManager has its
own independent copy of the image data, avoiding unintended side effects.
While it might involve additional memory and processing overhead, the
importance of data integrity justifies the choice.

class DynamicArrayManager {

private:

ComplexObject* data; // ComplexObject represents a complex data structure


like an image

public:

// Deep copy constructor

DynamicArrayManager(const DynamicArrayManager& other) {

size = other.size;

data = new ComplexObject[size];

for (int i = 0; i < size; ++i) {

data[i] = other.data[i]; // Assuming ComplexObject has an appropriate


copy constructor

};

In real-world scenarios, the choice between shallow and deep copy constructors
depends on the specific requirements of the application and the nature of the
objects being managed. Understanding the implications and trade-offs is crucial
for making informed design decisions.
Question#04
#include <iostream>

#include <string>

#include <iomanip>

using namespace std;

class Package

private:

string sender_name;

string sender_address;

string sender_city;

string sender_state;

string sender_ZIP;

string recipient_name;

string recipient_address;

string recipient_city;

string recipient_state;

string recipient_ZIP;
double weight;

double costperounce;

public:

Package(string sender_n, string sender_addr, string sender_c,

string sender_s, string sender_Z, string recipient_n, string recipient_addr,

string recipient_c,string recipient_s, string recipient_Z, double wei,

double cost);

void setsender_name(string sender_n);

string getsender_name();

void setsender_address(string sender_addr);

string getsender_address();

void setsender_city(string sender_c);

string getSendCity();

void setsender_state(string sender_s);

string getsender_state();

void setsender_ZIP(string sender_Z);

string getsender_ZIP();

void setrecipient_name(string recipient_n);

string getrecipient_name();

void setrecipient_address(string recipient_addr);


string getrecipient_address();

void setrecipient_city(string recipient_c);

string getrecipient_city();

void setrecipient_state(string recipient_s);

string getrecipient_state();

void setrecipient_ZIP(string recipient_Z);

string getrecipient_ZIP();

void setweight(double w);

double getweight();

void setcostperounce(double cost);

double getcostperounce();

double calculateCost();

};

Package::Package(string sender_n, string sender_addr, string sender_c, string

sender_s, string sender_Z, string recipient_n, string recipient_addr,string

recipient_c,string recipient_s, string recipient_Z, double wei, double cost)

sender_name = sender_n;

sender_address = sender_addr;

sender_city = sender_c;

sender_state = sender_s;

sender_ZIP = sender_Z;
recipient_name = recipient_n;

recipient_address = recipient_addr;

recipient_city = recipient_c;

recipient_state = recipient_s;

recipient_ZIP = recipient_Z;

if(wei > 0.0 && cost > 0.0)

weight = wei;

costperounce = cost;

else

weight = 0.0;

costperounce = 0.0;

void Package::setsender_name(string sender_n)

sender_name = sender_n;

string Package::getsender_name()

return sender_name;

}
void Package::setsender_address(string sender_addr)

sender_address = sender_addr;

string Package::getsender_address()

return sender_address;

void Package::setsender_city(string sender_c)

sender_city = sender_c;

string Package::getSendCity()

return sender_city;

void Package::setsender_state(string sender_s)

sender_state = sender_s;

string Package::getsender_state()

return sender_state;

void Package::setsender_ZIP(string sender_Z)


{

sender_ZIP = sender_Z;

string Package::getsender_ZIP()

return sender_ZIP;

void Package::setrecipient_name(string recipient_n)

recipient_name = recipient_n;

string Package::getrecipient_name()

return recipient_name;

void Package::setrecipient_address(string recipient_addr)

recipient_address = recipient_addr;

string Package::getrecipient_address()

return recipient_address;

void Package::setrecipient_city(string recipient_c)

recipient_city = recipient_c;
}

string Package::getrecipient_city()

return recipient_city;

void Package::setrecipient_state(string recipient_s)

recipient_state = recipient_s;

string Package::getrecipient_state()

return recipient_state;

void Package::setrecipient_ZIP(string recipient_Z)

recipient_ZIP = recipient_Z;

string Package::getrecipient_ZIP()

return recipient_ZIP;

void Package::setweight(double w)

weight = (w < 0.0 ) ? 0.0 : w;

double Package::getweight()
{

return weight;

void Package::setcostperounce(double cost)

costperounce = ( cost < 0.0) ? 0.0 : cost;

double Package::getcostperounce()

return costperounce;

double Package::calculateCost()

double result;

result = weight * costperounce;

return result;

class TwoDayPackage : public Package

private:

double two_day_delivery_fee;

public:
TwoDayPackage(string sender_n, string sender_addr, string

sender_c, string sender_s, string sender_Z, string recipient_n,

string recipient_addr,string recipient_c,string recipient_s,

string recipient_Z,double wei, double cost, double delivery_fee);

double gettwo_day_delivery_fee();

void settwo_day_delivery_fee(double delivery_fee);

double calculateCost();

};

TwoDayPackage::TwoDayPackage(string sender_n, string sender_addr,

string sender_c, string sender_s, string sender_Z, string recipient_n,

string recipient_addr,string recipient_c,string recipient_s,

string recipient_Z, double wei, double cost, double delivery_fee)

:Package(sender_n, sender_addr, sender_c, sender_s, sender_Z, recipient_n,

recipient_addr, recipient_c, recipient_s, recipient_Z,wei,cost)

settwo_day_delivery_fee(delivery_fee);

double TwoDayPackage::gettwo_day_delivery_fee()

return two_day_delivery_fee;

void TwoDayPackage::settwo_day_delivery_fee(double delivery_fee)

two_day_delivery_fee = delivery_fee;
}

double TwoDayPackage::calculateCost()

double result;

result = Package::calculateCost() + two_day_delivery_fee;

return result;

class OvernightPackage : public Package

private:

double overnight_delivery_fee;

public:

OvernightPackage(string sender_n, string sender_addr, string sender_c,

string sender_s, string sender_Z, string recipient_n, string recipient_addr,

string recipient_c,string recipient_s, string recipient_Z, double wei,

double cost, double delivery_fee);

double calculateCost();

double getovernight_delivery_fee();

void setovernight_delivery_fee(double delivery_fee);

};

OvernightPackage::OvernightPackage(string sender_n, string sender_addr,

string sender_c, string sender_s, string sender_Z, string recipient_n,

string recipient_addr,string recipient_c,string recipient_s,


string recipient_Z, double wei, double cost, double delivery_fee)

:Package(sender_n, sender_addr, sender_c, sender_s, sender_Z, recipient_n,

recipient_addr, recipient_c, recipient_s, recipient_Z,wei,cost)

setovernight_delivery_fee(delivery_fee);

double OvernightPackage::getovernight_delivery_fee()

return overnight_delivery_fee;

void OvernightPackage::setovernight_delivery_fee(double delivery_fee)

overnight_delivery_fee = delivery_fee;

double OvernightPackage::calculateCost()

double result;

result = (getcostperounce() + overnight_delivery_fee) * getweight();

return result;

int main(int argc, char *argv[])

OvernightPackage item1("Tom Brown", "123 Main Street", "Phoenix",

"Arizona", "89754", "John", "123 bent street", "Hartford", "Connecticut",

"87540", 12.00, 1.50, 1.10);

TwoDayPackage item2("Monique Smith", "987 1st Street", "Sacramento",


"California", "87654", "Paul", "833 palm Street", "Miami", "Florida",

"98763", 18.00, 1.05, 8.00);

cout << fixed << setprecision(2);

cout << "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n";

cout << "Overnight Delivery\n";

cout << "Sender " << item1.getsender_name()<< "\n";

cout << " " << item1.getsender_address() << "\n";

cout << " " << item1.getSendCity() << " " <<

item1.getsender_state() << " " << item1.getsender_ZIP() << "\n";

cout << "\n";

cout << "Recipient " << item1.getrecipient_name()<< "\n";

cout << " " << item1.getsender_address() << "\n";

cout << " " << item1.getrecipient_city() << " " <<

item1.getrecipient_state() << " " << item1.getrecipient_ZIP() << "\n";

cout << "Cost $ " <<item1.calculateCost() <<"\n";

cout << "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n";

cout << "\n\n";

cout << "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n";

cout << "2 Day Delivery\n";

cout << "Sender " << item2.getsender_name()<< "\n";

cout << " " << item2.getsender_address() << "\n";

cout << " " << item2.getSendCity() << " " <<

item2.getsender_state() << " " << item2.getsender_ZIP() << "\n";

cout << "\n";

cout << "Recipient " << item2.getrecipient_name()<< "\n";

cout << " " << item2.getsender_address() << "\n";

cout << " " << item2.getrecipient_city() << " " <<
item2.getrecipient_state() << " " << item2.getrecipient_ZIP() << "\n";

cout << "Cost $ " <<item2.calculateCost()<<"\n";

cout << "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n";

system("pause");

return 0;

You might also like