based

Opinionated utility library
git clone git://git.dimitrijedobrota.com/based.git
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING

commit f8a9080b0c3d26c6ae26868d7536d38534540a88
parent 4a0aeb1edb7f7261133d88b74c10328eecdb2cea
author Dimitrije Dobrota < mail@dimitrijedobrota.com >
date Sat, 15 Mar 2025 19:29:36 +0100

Proof of concept instrumented operation counter

Diffstat:
M .clang-tidy | ++++++ -
M CMakeLists.txt | + -
M example/empty_example.cpp | +++++++++++++++++++++ -
D include/based/based.hpp | ----------------------------------------------------------------------
A include/based/instrumentation.hpp | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
D source/based.cpp | --------------
A source/instrumentation.cpp | ++++++++++++++++++

7 files changed, 187 insertions(+), 87 deletions(-)


diff --git a/ .clang-tidy b/ .clang-tidy

@@ -10,9 +10,14 @@ Checks: "*,\

-llvm-header-guard,\
-llvm-include-order,\
-llvmlibc-*,\
-cppcoreguidelines-pro-bounds-constant-array-index,\
-modernize-use-nodiscard,\
-misc-include-cleaner,\
-misc-non-private-member-variables-in-classes,\
-modernize-use-trailing-return-type,\
-misc-non-private-member-variables-in-classes"
-*-magic-numbers,\
-*-use-ranges,\
"
WarningsAsErrors: ''
CheckOptions:
- key: 'bugprone-argument-comment.StrictMode'

diff --git a/ CMakeLists.txt b/ CMakeLists.txt

@@ -17,7 +17,7 @@ include(cmake/variables.cmake)


add_library(
based_based
source/based.cpp
source/instrumentation.cpp
)
add_library(based::based ALIAS based_based)

diff --git a/ example/empty_example.cpp b/ example/empty_example.cpp

@@ -1,4 +1,24 @@

auto main() -> int
#include <algorithm>
#include <format>
#include <iostream>
#include <vector>

#include "based/instrumentation.hpp"

int main()
{
using instrumented = based::instrumented<int>;

std::vector<int> base = {12, 5, 52, 5, 62, 46, 53, 73, 8, 83, 6};
std::vector<instrumented> vec(std::begin(base), std::end(base));

instrumented::initialize(vec.size());
std::sort(std::begin(vec), std::end(vec));

for (std::size_t i = 0; i < instrumented::size(); i++) {
std::cout << std::format(
"{:15}: {}\n", instrumented::name(i), instrumented::count(i));
}

return 0;
}

diff --git a/ include/based/based.hpp b/ include/based/based.hpp

@@ -1,70 +0,0 @@

#pragma once

#include <string>

#include "based/based_export.hpp"

/**
* A note about the MSVC warning C4251:
* This warning should be suppressed for private data members of the project's
* exported classes, because there are too many ways to work around it and all
* involve some kind of trade-off (increased code complexity requiring more
* developer time, writing boilerplate code, longer compile times), but those
* solutions are very situational and solve things in slightly different ways,
* depending on the requirements of the project.
* That is to say, there is no general solution.
*
* What can be done instead is understand where issues could arise where this
* warning is spotting a legitimate bug. I will give the general description of
* this warning's cause and break it down to make it trivial to understand.
*
* C4251 is emitted when an exported class has a non-static data member of a
* non-exported class type.
*
* The exported class in our case is the class below (exported_class), which
* has a non-static data member (m_name) of a non-exported class type
* (std::string).
*
* The rationale here is that the user of the exported class could attempt to
* access (directly, or via an inline member function) a static data member or
* a non-inline member function of the data member, resulting in a linker
* error.
* Inline member function above means member functions that are defined (not
* declared) in the class definition.
*
* Since this exported class never makes these non-exported types available to
* the user, we can safely ignore this warning. It's fine if there are
* non-exported class types as private member variables, because they are only
* accessed by the members of the exported class itself.
*
* The name() method below returns a pointer to the stored null-terminated
* string as a fundamental type (char const), so this is safe to use anywhere.
* The only downside is that you can have dangling pointers if the pointer
* outlives the class instance which stored the string.
*
* Shared libraries are not easy, they need some discipline to get right, but
* they also solve some other problems that make them worth the time invested.
*/

/**
* @brief Reports the name of the library
*
* Please see the note above for considerations when creating shared libraries.
*/
class BASED_EXPORT exported_class
{
public:
/**
* @brief Initializes the name field to the name of the project
*/
exported_class();

/**
* @brief Returns a non-owning pointer to the string stored in this class
*/
auto name() const -> char const*;

private:
BASED_SUPPRESS_C4251
std::string m_name;
};

diff --git a/ include/based/instrumentation.hpp b/ include/based/instrumentation.hpp

@@ -0,0 +1,141 @@

#pragma once

#include <array>
#include <cstddef>
#include <cstdint>

namespace based
{

struct instrumented_base
{
static void initialize(std::size_t size);

static auto size() { return number_ops; }
static auto name(std::size_t idx) { return names[idx]; }
static auto count(std::size_t idx) { return counts[idx]; }

protected:
enum operations : std::uint8_t
{
n,
constructor_default,
constructor_value,
constructor_copy,
constructor_move,
assignment_copy,
assignment_move,
destructor,
equality,
comparison,
_count, // NOLINT
};

static constexpr const std::size_t number_ops = _count;
static constexpr std::array<const char*, number_ops> names = {
"n",
"ctor_default",
"ctor_value",
"ctor_copy",
"ctor_move",
"asgn_copy",
"asgn_move",
"destructor",
"equality",
"comparison",
};

static std::array<double, number_ops> counts;
};

template<typename T>
// T is Semiregualr or Regular or TotallyOrdered
struct instrumented : instrumented_base
{
using value_type = T;

value_type value;

explicit instrumented(value_type val)
: value(std::move(val))
{
++counts[constructor_value];
}

explicit instrumented(value_type&& val)
: value(std::move(val))
{
++counts[constructor_value];
}

// Semiregular:
instrumented() { ++counts[constructor_default]; }

instrumented(const instrumented& val)
: value(val.value)
{
++counts[constructor_copy];
}

instrumented(instrumented&& val) noexcept
: value(std::move(val.value))
{
++counts[constructor_move];
}

// self assign should be handled by the value_type
instrumented& operator=(const instrumented& val) // NOLINT cert-oop54-cpp
{
++counts[assignment_copy];
value = val.value;
return *this;
}

// self assign should be handled by the value_type
instrumented& operator=(instrumented&& val) noexcept // NOLINT cert-oop54-cpp
{
++counts[assignment_move];
value = std::move(val.value);
return *this;
}

~instrumented() { ++counts[destructor]; }

// Regular

friend bool operator==(const instrumented& lhs, const instrumented& rhs)
{
++counts[equality];
return lhs.value == rhs.value;
}

friend bool operator!=(const instrumented& lhs, const instrumented& rhs)
{
return !(lhs == rhs);
}

// TotallyOrdered

friend bool operator<(const instrumented& lhs, const instrumented& rhs)
{
++counts[comparison];
return lhs.value < rhs.value;
}

friend bool operator>(const instrumented& lhs, const instrumented& rhs)
{
return rhs < lhs;
}

friend bool operator<=(const instrumented& lhs, const instrumented& rhs)
{
return !(rhs < lhs);
}

friend bool operator>=(const instrumented& lhs, const instrumented& rhs)
{
return !(lhs < rhs);
}
};

} // namespace based

diff --git a/ source/based.cpp b/ source/based.cpp

@@ -1,14 +0,0 @@

#include <format>
#include <string>

#include "based/based.hpp"

exported_class::exported_class()
: m_name {std::format("{}", "based")}
{
}

auto exported_class::name() const -> char const*
{
return m_name.c_str();
}

diff --git a/ source/instrumentation.cpp b/ source/instrumentation.cpp

@@ -0,0 +1,18 @@

#include <algorithm>
#include <iterator>

#include "based/instrumentation.hpp"

namespace based
{

std::array<double, instrumented_base::number_ops> instrumented_base::counts =
{};

void instrumented_base::initialize(std::size_t size)
{
std::fill(std::begin(counts), std::end(counts), 0.0);
counts[n] = static_cast<double>(size);
}

} // namespace based