tete du loic

 Loïc YON [KIUX]

  • Enseignant-chercheur
  • Référent Formation Continue
  • Responsable des contrats pros ingénieur
  • Référent entrepreneuriat
  • Responsable de la filière F2 ingénieur
  • Secouriste Sauveteur du Travail
mail
loic.yon@isima.fr
phone
(+33 / 0) 4 73 40 50 42
location_on
ISIMA
  • twitter
  • linkedin
  • viadeo

[C++] TP 5

Lire en Français

First publication date: 2020/04/27

The String class

Even if we study this class in the lecture, you have to implement it.

Here are the use cases :

int main(int, char **) {
   String s1;
   String s2(6);
   String s3("try");
   String s4(s3);
   String s5 = s3;

   s2 = s3;
   s3.replace("new");
   s3.display();
   cout << s3.c_str(); // this is a getter for the nested C string
   cout << s3[0];
   cout << s3;

   return 0;
}

You will have to develop it using unit testing and valgrind

You can download the exercice from here

Notes:

Constructor and destructor

TEST_CASE("Default constructor") {
   String c;
   REQUIRE(       -1 == c.getCapacity());
   REQUIRE(  nullptr == c.c_str()); // 0 or NULL for old compilers
}

We handle internally the String class as a C characters string : an array of char ending with the '\0' character. We need this information in the "guts" of the class. For the user of the class, the capacity is the maximum number that can be put in the string, that is why getCapacity() and capacity may differ.

Whenever it is possible, we need to qualify the methods as constant. We can check this with tests:

TEST_CASE("Check on the constness of accessors") {
   const String c;
   CHECK( -1       == c.getCapacity());
   CHECK(  nullptr == c.c_str()); 
}
TEST_CASE("from C string constructor") {
    char  source []= "nothing";
    String c1(source);
    CHECK( (signed) strlen(source) == c1.getCapacity() );
    CHECK( 0 == strcmp(source, c1.c_str()) ); 

    String c2 = "";
    CHECK( 0 == c2.getCapacity() );
    CHECK( 0 == strcmp("", c2.c_str())); 
}
TEST_CASE("Constructeur avec capacite") {
    String c1(6);
    CHECK( 6 == c1.getCapacity());
    CHECK( 0 == strlen(c1.c_str())); 

    // Is the allocated memory freed ?
}

Copy constructor

TEST_CASE("Copy constructor") {
    String s1(10);   // empty string
    String s2 = s1;  // another empty string
    
    CHECK( s1.getCapacity() == s2.getCapacity());
    // All the problems come from here
    // The void * cast is for the display of the catch library
    CHECK( (void *)s1.c_str() != (void *)s2.c_str() );
    CHECK( 0 == strcmp(s1.c_str(), s2.c_str() ));
}
TEST_CASE("display method") {
    const char * original = "a string to test";
    const String c1(original);
    std::stringstream ss;
    
    c1.display(); // is it compiling ?
    c1.display(std::cout); // compiling but not interest for testing
    c1.display(ss); // can be used for testing

    CHECK( ss.str() == original ); //  test of std::string :-)
}

the std::stringstream stream is a particluar stream of std::ostream. Instead of writing in files, we use std::stringstream for the tests that are easier to write.

Overload of the assignment operator

TEST_CASE("assigment operator") {
    String s1("first string");
    String s2("second string bigger than the first");
    
    s1 = s2;

    CHECK( s1.getCapacity() == s2.getCapacity()); 
    CHECK( (void *)s1.c_str() != (void *)s2.c_str() );
    CHECK( 0 == strcmp(s1.c_str(), s2.c_str() ));

    s1 = s1; // What is happening ?
}

Operators overloading(<<, [] et +)

TEST_CASE("<< overloading") {
  const char * str = "a new overloading";
  String s(str);
  std::stringstream ss;
  // ss << s;  // :-)

  CHECK( ss.str() == str ); //  test of std::string, again :-))
}

The tests are not written. It is up to you.

Handling errors will be added in the next lecture.

The vector : a dynamic array

The purpose of this exercice is to program a container , that is to say an object capable of containing others. The simplest container is the dynamic array known as vector : the capactity can change during le lifespan of the object. You will store double elements.

This data structure behaves like a regular array and allows direct access to elements when their position is known.

It encapsulates a classical static array of elements (of a given class). When the array is at full capacity, a new but bigger array is allocated and the content is copied from the old array to the new one.

The primer capacity can be given when the object is instanciated (default capacity : 10 for example). When the array becomes too small, its capacity is multiplied by 2.

The C function memcpy() is a super fast function to copy from an array to another one. You can also use std::copy()

The capacity() method gives the capacity of the vector ; the size() method returns the number of elements in the vector.

A method push_back() allows adding an element at the end of the vector and can trigger the vector extent.

You can write operators like the redirection, the index or concatenation

Do not forget to follow the normal form !

This container will be reused in the next lessons.

The testing code and the way to do it are described in the notice on the unit testing :

"Facts" checking

Did you check the following ?

void test1(Gossip b) {
  std::cout << "function call with copy";
}
Gossip test2a() {
  std::cout << "function call with return";
  return Gossip(); // creation of a local object
} // no copy anymore - See explanation in advanced course
Gossip test2b() {
  Gossip b; // creation of a local object
  std::cout << "function call with return";
  return b; 
} // no copy anymore - See explanation in advanced course 
void test3(Gossip& b) {
  std::cout << "function call with reference";
}
void test4(Gossip *b) {
  std::cout << "function call with pointer";
}

Type truncating

You have to check the type truncating and how to avoid it ...

void display1(Forme f) {
   f.display();
}

void display2(Forme &f) {
   f.display();
}

void display3(Forme * f) {
   f->display();
}

int main(int, char**) {
   Circle circle;
   
   display1(circle);
   display2(circle);
   display3(&circle);
   
   return 0;
}