First publication date: 2020/05/23
The canonical normal form
The following code whil helps us for analysing the behavior of the copy constructor and the assignment operator through inheritance and composition/aggregation. You can also reuse the code of the Gossip
class.
class M {
public:
M() {
std::cout << "M::M()" << std::endl;
}
~M() {
std::cout << "M::~M()" << std::endl;
}
M(const M&) {
std::cout << "M::M(const M&)" << std::endl;
}
};
class D : public M {
public:
D() {
std::cout << "D::D()" << std::endl;
}
~D() {
std::cout << "D::~D()" << std::endl;
}
/*
D(const D& d) {
std::cout << "D::D(const D&)" << std::endl;
}
*/
};
int main(int, char**) {
D d1;
D d2 = d1;
d1 = d2;
return 0;
}
Let us begin with the copy constructor
- Compile, run and analyse the log of the program
The default copy constructor from D
calls the copy constructor of M
.
- Uncomment the code from the
D
class. What do you notice ?
The copy constructor from M
is not called anymore : the default constructor is called .
- How do you restore the expected behavior ?
You need to explicitly the copy constructor from M
with the initilization list.
Let's consider now the assigment operator :
- Add such an operator to the
M
class - Compile and run. What do you notice ?
The default assignment operator of F
calls this operator.
- Add the operator to the class
F
.What do you notice ?
The assigment operator of class M
is not called anymore !
- How can be called the assigment operator from the
M
class from theF
class ?
If you have clicked here, you forgot what is written in the lesson with the contains()
method of Shape
, Circle
and Rectangle
.
The syntax is the following: Mother_class_name::method(parameters)
It now the time to end with aggregation...
- Copy the
M
class and rename it as aA
class. It is not clean but necessary ! - Add an attribute
A
toM
- Instanciate an objet
a1
with default values and create an objecta2
by copy. What do you notice ? - What can you do to have a proper copy ? You can also assign attributes with attributes. It works but it is not clean.
Exceptions
Add the exception mecanism to the classes of the last practical work, that is to know :
The exceptions we have to cope with are :
- allocation problems during initialization or when the object becomes bigger (
std::bad_alloc
oustd::invalid_argument
). - problems with indexes: you can create your own exception (
OutOfRangeException
) or use the standard exceptionstd::out_of_range
TEST_CASE("exceptions with bounds") {
String s(10);
REQUIRE_THROWS_AS( s[-1] == 0, String::OutOfRangeException);
// OU
REQUIRE_THROWS_AS( s[-1] == 0, std::out_of_range);
REQUIRE_THROWS_AS( s[12] == 0, std::bad_alloc); // :-)
}
- A adequate message if the intern pointer (
arr
) is null with an exception to definenull_pointer
(as derived class fromlogic_error
)
TEST_CASE("exceptions with null pointer") {
String s(0);
// check that inheritance is done
std::logic_error * pe = new null_pointer;
delete pe;
REQUIRE_THROWS_AS( s[1] == 0, null_pointer);
}
To detect a bad allocation, you may initialize an array with an gigenormous siez or you can do a very LOOOOOOONNNNG loop.
Program ending
Run the program below. You will see the different behaviors when quitting the program :
- natual ending (by default)
std::exit()
std::terminate()
std::unexpected()
(we should never call it directly)
class Gossip {
std::string name;
public :
Gossip(std::string n):name(n) {
std::cout << "constructor " << name << std::endl;
}
~Gossip() {
std::cout << "destructor " << name << std::endl;
}
};
Gossip g("global");
int main(int, char **) {
Gossip t("local");
static Gossip s("statlocal");
//std::exit(1);
//std::terminate();
//std::unexpected(); // you don't call it
return 0;
}
The Stack class
We will go on the study of containers with a stack of integers.
The first version will have a fixed size. The size will be choosen at initialization and an exception will emphasize the stack overflow.
You have to implement the following methods :
empty()
returns a boolean concerning the emptiness of the stackpush()
to add a new element on a stackpop()
removes the last element of the stacktop()
gives access to the last element- constructor(s) and destructor
You will reuse this class in the TP on templates. You will find some tests :
TEST_CASE("default constructor") {
Stack s; // implies that the default size is not null
CHECK( s.empty() );
CHECK( 0 == s.size() );
}
TEST_CASE("Bad construction exception") {
REQUIRE_THROWS_AS( Stack(-1).empty(), std::invalid_argument );
REQUIRE_THROWS_AS( Stack( 0).empty(), std::invalid_argument );
}
TEST_CASE("Empty stack exception") {
REQUIRE_THROWS_AS( Stack().pop(), std::invalid_argument );
}
TEST_CASE("living stack") {
Stack s(10);
CHECK( s.empty() );
CHECK( 0 == s.size() );
s.push(5);
CHECK( !s.empty() );
CHECK( 1 == s.size() );
CHECK( 5 == s.top() );
s.push(2);
s.push(1);
CHECK( 3 == s.size() );
CHECK( 1 == s.top() );
s.pop();
CHECK( 2 == s.size() );
CHECK( 2 == s.top() );
s.pop();
s.pop();
CHECK( 0 == s.size() );
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10