Skip to content

Latest commit

 

History

History
188 lines (167 loc) · 7.27 KB

tutorial.md

File metadata and controls

188 lines (167 loc) · 7.27 KB

Tutorial

Here is the tutorial code. There is compilable code in folder tests/tutorial/
The code is well commented. Please read the comment to see the tutorial.

// Include the header
#include "accessorpp/accessor.h"

#include "tutorial.h"

#include <iostream>

TEST_CASE("Accessor tutorial 1, basic")
{
    std::cout << std::endl << "Accessor tutorial 1, basic" << std::endl;

    // The namespace is accessor.
    // The first parameter is the type of the underlying value.
    // By default, the accessor stores the value in internal storage.
    // The argument 0 is the initial value, it can be omitted
    // and access will use the default value, i.e, 0 for int, "" for string, etc.
    accessorpp::Accessor<int> accessor(0);

    // The argument can be omitted, and the code will look lik,
    // accessorpp::Accessor<int> accessor;

    // To obtain the underlying value, we can cast the accessor to the type
    std::cout
        << "Accessor tutorial 1: the default value should be 0, got "
        << (int)accessor
        << std::endl;

    // Now let's set a new value to the accessor using the assignment operator = .
    accessor = 3;
    // We can also call Accessor::get() to obtain the underlying value
    std::cout
        << "Accessor tutorial 1: the new value should be 3, got "
        << accessor.get()
        << std::endl;

    // We can also call Accessor::set() to set the underlying value.
    accessor.set(8);
    // Accessor supports stream operator directly, so we don't need to
    // obtain the underlying value.
    std::cout
        << "Accessor tutorial 1: the new value should be 8, got "
        << accessor
        << std::endl;
}

TEST_CASE("Accessor tutorial 2, customized getter/setter")
{
    std::cout << std::endl << "Accessor tutorial 2, customized getter/setter" << std::endl;

    {
        // Let's create an accessor with customized getter and setter.
        // The first argument is the getter, the second is the setter.
        // Note: the code requires C++20 standard in MSVC, otherwise
        // it gives error "lambda capture variable not found".
        // The code works with GCC and Clang.
        accessorpp::Accessor<int> accessor(
            // This is the getter
            [&accessor]() { return accessor.directGet();  },
            // This is the setter, it multiplies the incoming value with 2 
            // and store to the accessor, for fun.
            [&accessor](const int value) { accessor.directSet(value * 2);  }
        );
        accessor = 3;
        std::cout
            << "Accessor tutorial 2: the accessor is set with 3, now the value should be 6, got "
            << (int)accessor
            << std::endl;
    }

    {
        // Now let's create an accessor with default getter and customized setter.
        // The first argument is accessorpp::defaultGetter, it uses the getter supplied by the accessor,
        // the second is the setter, the third argument is the inital value.
        // Likewise, there is accessorpp::defaultSetter for the default getter.
        // This time we use the syntax that MSVC is happy with.
        accessorpp::Accessor<int> * ptr;
        accessorpp::Accessor<int> accessor(
            accessorpp::defaultGetter,
            // Must capture ptr as reference, otherwise it won't get the value set later.
            [&ptr](const int value) { ptr->directSet(value + 1);  },
            5
        );
        ptr = &accessor;
        std::cout
            << "Accessor tutorial 2: the initial accessor is 5, got "
            << (int)accessor
            << std::endl;
        accessor = 8;
        std::cout
            << "Accessor tutorial 2: the accessor is set with 8, now the value should be 9, got "
            << accessor
            << std::endl;
    }

    {
        // Now let's create a read-only accessor with default getter.
        // The first argument is accessorpp::defaultGetter, it uses the getter supplied by the accessor,
        // the second is accessorpp::noSetter, it means no setter available, thus the accessor is read-only.
        accessorpp::Accessor<int> accessor(
            accessorpp::defaultGetter,
            accessorpp::noSetter,
            5
        );
        std::cout << std::boolalpha;
        std::cout
            << "Accessor tutorial 2: the initial accessor is 5, got "
            << accessor
            << std::endl;
        std::cout
            << "Accessor tutorial 2: isReadOnly() should be true, got "
            << accessor.isReadOnly()
            << std::endl;
        try {
            // Assigning to read-only accessor will throw exception std::logic_error
            accessor = 8;
        }
        catch(const std::logic_error &) {
            std::cout
                << "Accessor tutorial 2: set to the read-only accessor causes std::logic_error, we got here."
                << std::endl;
        }
    }
}

TEST_CASE("Accessor tutorial 3, external storage")
{
    std::cout << std::endl << "Accessor tutorial 3, external storage" << std::endl;

    // To use external storage, we need to define the policies which is a struct.
    struct MyPolicies
    {
        // The type Storage determines how the value is stored.
        // If Storage is accessorpp::InternalStorage, the Accessor stores the value inside the Accessor.
        // If Storage is accessorpp::ExternalStorage, the Accessor doesn't store the value, and
        // how the underlying value is accessed completely depends on the getter and setter.
        using Storage = accessorpp::ExternalStorage;
    };

    int value = 38;
    // Pass MyPolicies as the second template argument, and pass &value as the getter and setter.
    // The access will read from 'value', and set to 'value'.
    accessorpp::Accessor<int, MyPolicies> accessor(&value, &value);
    std::cout
        << "Accessor tutorial 3: the initial value is 38, accessor = "
        << accessor
        << std::endl;
    accessor = 8;
    std::cout
        << "Accessor tutorial 3: the accessor is set with 8, now both value and accessor should be 8, got"
        << " value = " << value << " and accessor = " << (int)accessor
        << std::endl;
}

TEST_CASE("Accessor tutorial 4, on change event")
{
    std::cout << std::endl << "Accessor tutorial 4, on change event" << std::endl;

    // To use onChanging and onChanged events, we need to define the policies which is a struct.
    struct MyPolicies
    {
        // Use std::function<void (int)> as OnChangingCallback,
        // and leave OnChangedCallback default which will not trigger onChanged event.
        // Here we use std::function for demostration purpose,
        // you should use the CallbackList class in eventpp library,
        // https://github.com/wqking/eventpp
        using OnChangingCallback = std::function<void (int)>;
    };
    accessorpp::Accessor<int, MyPolicies> accessor;
    // Assign the onChanging event callback. If we use eventpp::CallbackList here,
    // we can add multiple callbacks to the event.
    accessor.onChanging() = [&accessor](const int newValue) {
        std::cout << "Accessor tutorial 4: the onChanging event got new value " << newValue
            << " and old value is " << accessor << std::endl;
    };

    // In onChanging, the newValue will be 8, and old value is 0
    accessor = 8;
    // In onChanging, the newValue will be 38, and old value is 8
    accessor = 38;
}