C++ Design by Contract Library Documentation

Garcia Justo - Insfrán Jordán F. - Diaz Zamboni Javier E.

This library enables you to specify and enforce contracts (preconditions, postconditions, and invariants) in your C++ code, aiming to promote robustness and reliability.

Table of Contents

Installation

To integrate the DbC library into your C++ project, follow these installation steps:

  • Download raw files from GitHub
wget https://raw.githubusercontent.com/FIUNER-LICA/biblioteca-dbc-cpp/main/contract-lib/contract.h https://raw.githubusercontent.com/FIUNER-LICA/biblioteca-dbc-cpp/main/contract-lib/contract.cpp
  • Include the contract.h header file in your project.
#include "contract.h"
  • Ensure your compiler supports C++11 or later.

Basic Usage

Here's a basic example to get you started with the DbC library using pre and post conditions as functions:

#include <iostream>
#include "contract.h"

using namespace contract;

void example_function(int x) {
    precondition(x > 0, "x must be greater than 0");

    // Function logic here

    postcondition(x < 100, "x should be less than 100");
}

int main() {
    try {
        example_function(50);
    } catch (const violation_error& e) {
        std::cerr << "Contract violation: " << e.what() << std::endl;
    }

    return 0;
}

Features

Preconditions

Preconditions specify conditions that must be true at the entry of a function or method. Here's how to use preconditions effectively:

  • Function Syntax:
    • As function: precondition(expression, message)
    • As class: precond(expression, message)
    • As macro: REQUIRE(expression, message)

Example:

void example_function(int x) {
    precond(x > 0, "x must be greater than 0"); // As class

    precondition(x > 0, "x must be greater than 0"); // As function

    REQUIRE(x > 0, "x must be greater than 0"); // As macro

    // Function logic.
}

Postconditions

Postconditions specify conditions that must be true at the exit of a function or method. They validate the expected outcomes and state changes after the execution of a routine. Here's how to use postconditions effectively:

  • Function Syntax:
    • As function: postcondition(expression, message)
    • As class: postcond(expression, message)
    • As macro: ENSURE(expression, message)

Example:

void example_function(int x) {    
    //Function logic.   

    postcondition(x < 100, "x should be less than 100"); // As function

    postcond(x < 100, "x should be less than 100"); // As class

    ENSURE(x < 100, "x should be less than 100"); // As macro
}

Invariants

Invariants specify conditions that must be true for a class or object, ensuring its integrity throughout its lifecycle. Here's how to use invariants effectively:

Declaring invariants

To include invariants in your class, two steps are required:

  1. Inheritating from contract::Invariant<T>
  2. Declare invariants within the class (tipically in the constructor)
class Some_class : protected contract::Invariant<Some_class> // Inherit 
{                                                            // from contract::Invariant<T>
    public:
        Some_class();
        void some_method();
}

Some_class::Some_class() {
    add_invariant(INVARIANT(bool_exp)); // Using macro

    add_invariant([&](){ return bool_exp;   }); // Using lambda
}

Checking invariants

To verify that the invariants remain true after an operation that might compromise their consistency, you must call check_invariant().

void Some_class::some_method()
{
    // Method logic

    check_invariant(); // Force invariants checks
}

Eror handling

If a condition is violated, an exception will be thrown. You can catch these exceptions to handle errors gracefully, such as logging the issue or attempting to recover.

try {
    some_function(15);
} catch (const violation_error& e) {
    std::cerr<<"Contract violation: "<< e.what() << std::endl;
    // Error handling
}