Guidelines to implement a C++ library

A private object Private::GeometricElementFactory. GeometricElement given an identifier. ‣ Each such object gets a ReadMeditFormat() that calls under the ...
2MB taille 5 téléchargements 268 vues
Guidelines to implement a C++ library Sébastien Gilles!

Outline •

Quick overlook at object programming ‣ A glimpse at C++ ‣ What is object programming?



Principles to keep in mind in a library ‣ “Easy to use and hard to misuse" •

The different levels of error handling

‣ DRY principle (Don’t Repeat Yourself) •

A really useful C++ concept: RAII ‣ Smart pointers

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

A quick glimpse at C++ •

Compiled language ‣ The source files are compiled in object files (*.o) ‣ Source files can be combined in libraries (.a, .so, .dylib, .dll …) ‣ An executable can be created by a source file including a “main” function that is linked with one or several libraries. More efficient code

Strenuous build process

Static check

Output is specific to a given architecture

Warnings

Compilation takes time

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

A quick glimpse at C++ •

Compiled language



Strongly typed # a x s

In Python = 5 = 7. = "My string"

// In C++ #include #include #include

!

int a = 5; double x = 7.; std::string s = "My string";

std::map my_object; std::map::iterator it = my_object.begin(); // New in C++ 11: type inference auto a2 = 5; auto x2 = 7.; auto s2 = "My string"; // const char*, not std::string! auto it2 = my_object.begin();

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

A quick glimpse at C++ •

Compiled language



Strongly typed



No garbage collection int* array = new int[5]; // assign a dynamic array ... delete[] array; // mandatory line to avoid memory leak!

Ellis and Stroustrup wrote in The Annotated C++ Reference Manual: "C programmers think memory management is too important to be left to the computer. Lisp programmers think memory management is too important to be left to the user."

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

A quick glimpse at C++ •

Compiled language



Strongly typed



No garbage collection



Object programming

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

What is object programming? •

In C ‣ “POD” types void, char, int, short int, unsigned int, …, float, double

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

What is object programming? •

In C ‣ “POD” types ‣ Pointer: address in memory of an element For instance for a dynamic array: size_t Nelement = 5; double* array; array = (double*) malloc(Nelement * sizeof(double)); ... free(array);

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

What is object programming? •

In C ‣ “POD” types ‣ Pointer: address in memory of an element ‣ Toward the objects: struct

struct Square { float side_length; float area; };

void Print(struct Square* square) { assert(square); printf("The length of a side is %f so the area is %f\n", square->side_length, square->area); }

int main() { struct Square square; square.side_length = 5.f; square.area = square.side_length * square.side_length; Print(&square);

! ! ! ! ! !

The length of a side is 5.000000 so the area is 25.000000 square.side_length = 10.f; Print(&square); The length of a side is 10.000000 so the area is 25.000000

}

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

What is object programming? // In Square.hpp class Square { public: //! Constructor. explicit Square(float side_length); //! Change the length of a side. void SetSideLength(float side_length); //! Returns the length of a side. float GetSideLength() const; //! Returns the area. float GetArea() const; private:

!

};

//! Length of a side. float side_length_; //! Area. float area_;

!

//! Print informations related to \a square. void Print(const Square& square);

Sébastien Gilles

// In Square.cpp Square::Square(float side_length) { SetSideLength(side_length); }

!

void Square::SetSideLength(float side_length) { side_length_ = side_length; area_ = side_length * side_length; }

!

float Square::GetSideLength() const { return side_length_; }

!

float Square::GetArea() const { return area_; }

!

void Print(const Square& square) { std::cout HyperelasticityParameters;

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

DRY principle •

Why this principle?



How to enforce it? ‣ Function or class that put the functionality in common. ‣ Inheritance. ‣ Templates ‣ Maybe a revision of the hierarchy of classes. ‣ In extreme cases, code generator.

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

RAII idiom! “Resource acquisition is initialisation”

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

RAII idiom •

Without it, very difficult to manage correctly ressources:

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

RAII idiom •

Without it, very difficult to manage correctly ressources: int Function() { double* array = new array[5]; // ... if (condition) { delete[] array; return 1; } // ... try { OtherFunctionThatMightThrow(); } catch(...) { delete[] array; throw; }

}

delete[] array; return 0;

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

RAII idiom •

Without it, very difficult to manage correctly ressources:



Principle: create an object which constructor allocates the ressource and which destructor deallocates it. // In ArrayRaii.hpp class ArrayRaii { public: //! Constructor. explicit ArrayRaii(std::size_t size); //! Destructor. ~ArrayRaii(); //! Accessor to the underlying array. double* GetArray() const; private:

};

//! Underlying array. double* array_;

Sébastien Gilles

// In ArrayRaii.cpp ArrayRaii::ArrayRaii(std::size_t size) { array_ = new double(size); }

!

ArrayRaii::~ArrayRaii() { delete[] array_; }

! !

double* ArrayRaii::GetArray() const { return array_; }

M3DISIM Seminar - the 7th of February 2014

RAII idiom •

Without it, very difficult to manage correctly ressources:



Principle: create an object which constructor allocates the ressource and which destructor deallocates it. Without RAII int Function() { double* array = new array[5]; // ... if (condition) { delete[] array; return 1; } // ... try { OtherFunctionThatMightThrow(); } catch(...) { delete[] array; throw; }

!

}

With RAII int Function() { ArrayRaii array(5); // ... if (condition) return 1; // ... OtherFunctionThatMightThrow(); }

return 0;

delete[] array; return 0;

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

RAII idiom •

Without it, very difficult to manage correctly ressources:



Principle: create an object which constructor allocates the ressource and which destructor deallocates it.



Very common in the Standard Template Library. ‣ std::vector ‣ std::ifstream, std::ofstream ‣ std::shared_ptr

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

RAII idiom •

Without it, very difficult to manage correctly ressources:



Principle: create an object which constructor allocates the ressource and which destructor deallocates it.



Very common in the Standard Template Library.



Useful to encapsulate existing C library. ‣ Petsc: VecCreateMPI/VecDestroy ‣ OpenMPI: Init()/Finalize()

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

Smart pointer •

Pointers are tricky to use with classes ‣ Pointers can’t easily be allocated or deallocated within a class. ‣ Deletion of the Label object must occur outside the class. class Label { public: explicit Label(int label) : label_(label) { } private: };

int label_;

class GeometricElement { public: explicit GeometricElement(Label* label) : label_(label) { } ~GeometricElement() { delete label_; } GeometricElement& operator=(const GeometricElement&) = default; private: Label* label_; };

// main.cpp int main() { Label* label = new Label(1); GeometricElement element1(label); GeometricElement element2 = element1; // ERROR: double delete! }

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

Smart pointer •

Pointers are tricky to use with classes ‣ Pointers can’t easily be allocated or deallocated within a class. ‣ Deletion of the Label object must occur outside the class. class Label { public: explicit Label(int label) : label_(label) { } private: };

int label_;

// Patched version. class GeometricElement { public: explicit GeometricElement(Label* label) : label_(label) { } ~GeometricElement() = default; GeometricElement& operator=(const GeometricElement&) = default; private: Label* label_; };

// Patched version. int main() { Label* label = new Label(1); GeometricElement element1(label); GeometricElement element2 = element1; delete label; }

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

Smart pointer •

Pointers are tricky to use with classes ‣ Pointers can’t easily be allocated or deallocated within a class. ‣ Deletion of the Label object must occur outside the class. ‣ In a complex hierarchy, it can lead to extensive bookkeeping to make sure each object is properly deleted and that this deletion happens only once. ‣ … which is somehow contradictory with the encapsulation principle.

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

Smart pointer •

Pointers are tricky to use with classes



But we definitely want to use pointers instead of objects directly ‣ Pointers are much lightweight to use than objects. ‣ We want to be able to link a given object to several other ones. ‣ and simply copying objects wouldn’t do: any modification wouldn’t be propagated!

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

Smart pointer •

Pointers are tricky to use with classes



But we definitely want to use pointers instead of objects directly



Solution: std::shared_ptr (C++ 11) ‣ RAII idiom applied here. ‣ std::shared_ptr keeps count on how many pointers track a given object, and deletes it when this counter reaches 0. class GeometricElement { public: explicit GeometricElement(std::shared_ptr label) : label_(label) { } ~GeometricElement() = default; GeometricElement& operator=(const GeometricElement&) = default; private: std::shared_ptr label_; };

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

Smart pointer •

Pointers are tricky to use with classes



But we definitely want to use pointers instead of objects directly



Solution: std::shared_ptr (C++ 11) ‣ RAII idiom applied here. ‣ std::shared_ptr keeps count on how many pointers track a given object, and deletes it when this counter reaches 0. #include

!

int main() { { std::shared_ptr label = std::make_shared(2); // internal count = 1 GeometricElement element1(label); // internal count = 2 { GeometricElement element2 = element1; // internal count = 3 } // element2 goes out of scope; internal count = 2 } // underlying object is destroyed here! }

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

Smart pointer •

Pointers are tricky to use with classes



But we definitely want to use pointers instead of objects directly



Solution: std::shared_ptr (C++ 11)



Smart pointers are the most common storage used in HappyHeart! ‣ Vectors of smart pointers are especially handy:

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014

Smart pointer •

Pointers are tricky to use with classes



But we definitely want to use pointers instead of objects directly



Solution: std::shared_ptr (C++ 11)



Smart pointers are the most common storage used in HappyHeart! ‣ Vectors of smart pointers are especially handy: class GeometricMeshRegion { ... private: std::vector vertice_list_; };

With syntactic sugar: class Vertex { typedef std::vector vector_shared_ptr; };

! !

class GeometricMeshRegion { ... private: Vertex::vector_shared_ptr vertice_list_; // syntactic sugar! };

Sébastien Gilles

M3DISIM Seminar - the 7th of February 2014