Tracker for object construction, copy and movement











up vote
0
down vote

favorite












I made an object tracker for debugging and testing purposes called ccm_counter (construction, copy, move counter). It counts constructor, copy and move calls. It can be used to detect inefficient forwarding and unnecessary (or unintended) copies. The behavior is as follows:



What it does



Let vector<A> be the type you want to test and A its element type (A must be move and copy constructable and must not be final). Drop in the counter like vector<ccm_counter<A>>. ccm_counter behaves like A except that it also counts:




  1. constructor calls

  2. copy constructor calls

  3. move constructor calls

  4. copy assignment calls

  5. move assignment calls

  6. copies

  7. moves


Point 1 to 5 are bound to the object (or variable) while point 6 and 7 are bound to the value of the object. For example:



struct A{}; 
ccm_counter<A> a, b; // a and b increment constructor call count by 1
b = a; // a +1 copies -> import copies and moves from a -> b +1 copy assignment
a = b; // b +1 copies -> import copies and moves from b -> a +1 copy assignment
b = a;


This example results in (shortened)



a:
constructor calls: 1
copy assignment calls: 1
total copies made: 3

b:
constructor calls: 1
copy assignment calls: 2
total copies made: 3


Now we add the vector<A> (the examples uses std::vector):



struct A{}; 
vector<ccm_counter<A>> vec;
ccm_counter<A> a, b;
vec.shrink_to_fit();
vec.push_back(a);
vec.push_back(b);

for (int i = 0; i < vec.size(); i++)
cout << "element " << i << ":n" << vec[i].get_ccmd_stats() << "nn";


which prints:



element 0:
constructor calls: 0
copy constructor calls: 0
move constructor calls: 1
copy assignment calls: 0
move assignment calls: 0
total copies made: 1
total moves made: 1

element 1:
constructor calls: 0
copy constructor calls: 1
move constructor calls: 0
copy assignment calls: 0
move assignment calls: 0
total copies made: 1
total moves made: 0


The constructor and assignment call counts are not very useful in this example, because they are bound to the object itself, which, in this case, is just an element in the internal buffer of the vector (which also changes as soon as the buffer is reallocated). However, the total copies and moves were tracked by the value. Element 0 gets copied when we call vec.push_back(a). Element 1 gets copied when we call vec.push_back(b). Because the vector exceeds his size each time we call vec.push_back(...), its internal buffer has to be reallocated each time. This results in element 0 beeing moved into the new buffer which is reflected in the output.



Implementation



#include <type_traits>
#include <ostream>
#include <iomanip>
#include <algorithm>
#include <cmath>

struct ccm_stats
{
std::size_t ctor = 0; // number of (any, except copy and move) constructor calls
std::size_t copy_ctor = 0; // number of copy constructor calls
std::size_t copy_assign = 0; // number of copy assignment calls
std::size_t move_ctor = 0; // number of move constructor calls
std::size_t move_assign = 0; // number of move assignment calls
std::size_t copies = 0; // number of copies made
std::size_t moves = 0; // number of moves made

constexpr ccm_stats& operator+=(const ccm_stats& rhs)
{
ctor += rhs.ctor;
copy_ctor += rhs.copy_ctor;
move_ctor += rhs.move_ctor;
copy_assign += rhs.copy_assign;
move_assign += rhs.move_assign;
copies += rhs.copies;
moves += rhs.moves;
return *this;
}

constexpr ccm_stats operator+(const ccm_stats& rhs)
{
ccm_stats ret(*this);
return ret += rhs;
}
};

std::ostream& operator<<(std::ostream& os, const ccm_stats& stats)
{
using namespace std;
constexpr size_t sw = 24;
const size_t nw = static_cast<size_t>(
log10(max({ stats.ctor, stats.copy_ctor, stats.copy_assign, stats.move_ctor, stats.move_assign, stats.copies, stats.moves }))) + 1;

os
<< setw(sw) << left << "constructor calls: " << setw(nw) << right << stats.ctor << "n"
<< setw(sw) << left << "copy constructor calls: " << setw(nw) << right << stats.copy_ctor << "n"
<< setw(sw) << left << "move constructor calls: " << setw(nw) << right << stats.move_ctor << "n"
<< setw(sw) << left << "copy assignment calls: " << setw(nw) << right << stats.copy_assign << "n"
<< setw(sw) << left << "move assignment calls: " << setw(nw) << right << stats.move_assign << "n"
<< setw(sw) << left << "total copies made: " << setw(nw) << right << stats.copies << "n"
<< setw(sw) << left << "total moves made: " << setw(nw) << right << stats.moves;
return os;
}

// A wrapper object that inherits from `T` and counts construction, copy and move operations of `T`.
template <typename T>
class ccm_counter : public T
{
public:
template <typename ...Args, typename = std::enable_if_t<std::is_constructible_v<T, Args...>>>
constexpr explicit ccm_counter(Args... args) noexcept(std::is_nothrow_constructible_v<T, Args...>)
: T(std::forward<Args>(args)...)
{
_stats.ctor++;
}

constexpr ccm_counter(const ccm_counter& other) noexcept(std::is_nothrow_copy_constructible_v<T>)
: T(other)
{
static_assert(std::is_copy_constructible_v<T>, "T must be copy constructible.");
other._stats.copies++;
_import_stats(other);
_stats.copy_ctor++;
}

constexpr ccm_counter(ccm_counter&& other) noexcept(std::is_nothrow_move_constructible_v<T>)
: T(other)
{
static_assert(std::is_move_constructible_v<T>, "T must be move constructible.");
other._stats.moves++;
_import_stats(other);
_stats.move_ctor++;
}

constexpr auto operator=(const ccm_counter& other) noexcept(std::is_nothrow_copy_assignable_v<T>)
-> std::enable_if_t<std::is_copy_assignable_v<T>, ccm_counter&>
{
T::operator=(other);
other._stats.copies++;
_import_stats(other);
_stats.copy_assign++;
return *this;
}

constexpr auto operator=(ccm_counter&& other) noexcept(std::is_nothrow_move_assignable_v<T>)
-> std::enable_if_t<std::is_move_assignable_v<T>, ccm_counter&>
{
T::operator=(other);
other._stats.moves++;
_import_stats(other);
_stats.move_assign++;
return *this;
}

[[nodiscard]] constexpr ccm_stats get_ccmd_stats() const noexcept
{
return _stats;
}

constexpr void set_ccmd_stats(ccm_stats stats)
{
_stats = std::move(stats);
}

private:
constexpr void _import_stats(const ccm_counter& source)
{
_stats.copies = source._stats.copies;
_stats.moves = source._stats.moves;
}

mutable ccm_stats _stats{};
};


template <typename T>
std::ostream& operator<<(std::ostream& os, const ccm_counter<T>& counter)
{
return os << counter.get_ccmd_stats();
}









share|improve this question


























    up vote
    0
    down vote

    favorite












    I made an object tracker for debugging and testing purposes called ccm_counter (construction, copy, move counter). It counts constructor, copy and move calls. It can be used to detect inefficient forwarding and unnecessary (or unintended) copies. The behavior is as follows:



    What it does



    Let vector<A> be the type you want to test and A its element type (A must be move and copy constructable and must not be final). Drop in the counter like vector<ccm_counter<A>>. ccm_counter behaves like A except that it also counts:




    1. constructor calls

    2. copy constructor calls

    3. move constructor calls

    4. copy assignment calls

    5. move assignment calls

    6. copies

    7. moves


    Point 1 to 5 are bound to the object (or variable) while point 6 and 7 are bound to the value of the object. For example:



    struct A{}; 
    ccm_counter<A> a, b; // a and b increment constructor call count by 1
    b = a; // a +1 copies -> import copies and moves from a -> b +1 copy assignment
    a = b; // b +1 copies -> import copies and moves from b -> a +1 copy assignment
    b = a;


    This example results in (shortened)



    a:
    constructor calls: 1
    copy assignment calls: 1
    total copies made: 3

    b:
    constructor calls: 1
    copy assignment calls: 2
    total copies made: 3


    Now we add the vector<A> (the examples uses std::vector):



    struct A{}; 
    vector<ccm_counter<A>> vec;
    ccm_counter<A> a, b;
    vec.shrink_to_fit();
    vec.push_back(a);
    vec.push_back(b);

    for (int i = 0; i < vec.size(); i++)
    cout << "element " << i << ":n" << vec[i].get_ccmd_stats() << "nn";


    which prints:



    element 0:
    constructor calls: 0
    copy constructor calls: 0
    move constructor calls: 1
    copy assignment calls: 0
    move assignment calls: 0
    total copies made: 1
    total moves made: 1

    element 1:
    constructor calls: 0
    copy constructor calls: 1
    move constructor calls: 0
    copy assignment calls: 0
    move assignment calls: 0
    total copies made: 1
    total moves made: 0


    The constructor and assignment call counts are not very useful in this example, because they are bound to the object itself, which, in this case, is just an element in the internal buffer of the vector (which also changes as soon as the buffer is reallocated). However, the total copies and moves were tracked by the value. Element 0 gets copied when we call vec.push_back(a). Element 1 gets copied when we call vec.push_back(b). Because the vector exceeds his size each time we call vec.push_back(...), its internal buffer has to be reallocated each time. This results in element 0 beeing moved into the new buffer which is reflected in the output.



    Implementation



    #include <type_traits>
    #include <ostream>
    #include <iomanip>
    #include <algorithm>
    #include <cmath>

    struct ccm_stats
    {
    std::size_t ctor = 0; // number of (any, except copy and move) constructor calls
    std::size_t copy_ctor = 0; // number of copy constructor calls
    std::size_t copy_assign = 0; // number of copy assignment calls
    std::size_t move_ctor = 0; // number of move constructor calls
    std::size_t move_assign = 0; // number of move assignment calls
    std::size_t copies = 0; // number of copies made
    std::size_t moves = 0; // number of moves made

    constexpr ccm_stats& operator+=(const ccm_stats& rhs)
    {
    ctor += rhs.ctor;
    copy_ctor += rhs.copy_ctor;
    move_ctor += rhs.move_ctor;
    copy_assign += rhs.copy_assign;
    move_assign += rhs.move_assign;
    copies += rhs.copies;
    moves += rhs.moves;
    return *this;
    }

    constexpr ccm_stats operator+(const ccm_stats& rhs)
    {
    ccm_stats ret(*this);
    return ret += rhs;
    }
    };

    std::ostream& operator<<(std::ostream& os, const ccm_stats& stats)
    {
    using namespace std;
    constexpr size_t sw = 24;
    const size_t nw = static_cast<size_t>(
    log10(max({ stats.ctor, stats.copy_ctor, stats.copy_assign, stats.move_ctor, stats.move_assign, stats.copies, stats.moves }))) + 1;

    os
    << setw(sw) << left << "constructor calls: " << setw(nw) << right << stats.ctor << "n"
    << setw(sw) << left << "copy constructor calls: " << setw(nw) << right << stats.copy_ctor << "n"
    << setw(sw) << left << "move constructor calls: " << setw(nw) << right << stats.move_ctor << "n"
    << setw(sw) << left << "copy assignment calls: " << setw(nw) << right << stats.copy_assign << "n"
    << setw(sw) << left << "move assignment calls: " << setw(nw) << right << stats.move_assign << "n"
    << setw(sw) << left << "total copies made: " << setw(nw) << right << stats.copies << "n"
    << setw(sw) << left << "total moves made: " << setw(nw) << right << stats.moves;
    return os;
    }

    // A wrapper object that inherits from `T` and counts construction, copy and move operations of `T`.
    template <typename T>
    class ccm_counter : public T
    {
    public:
    template <typename ...Args, typename = std::enable_if_t<std::is_constructible_v<T, Args...>>>
    constexpr explicit ccm_counter(Args... args) noexcept(std::is_nothrow_constructible_v<T, Args...>)
    : T(std::forward<Args>(args)...)
    {
    _stats.ctor++;
    }

    constexpr ccm_counter(const ccm_counter& other) noexcept(std::is_nothrow_copy_constructible_v<T>)
    : T(other)
    {
    static_assert(std::is_copy_constructible_v<T>, "T must be copy constructible.");
    other._stats.copies++;
    _import_stats(other);
    _stats.copy_ctor++;
    }

    constexpr ccm_counter(ccm_counter&& other) noexcept(std::is_nothrow_move_constructible_v<T>)
    : T(other)
    {
    static_assert(std::is_move_constructible_v<T>, "T must be move constructible.");
    other._stats.moves++;
    _import_stats(other);
    _stats.move_ctor++;
    }

    constexpr auto operator=(const ccm_counter& other) noexcept(std::is_nothrow_copy_assignable_v<T>)
    -> std::enable_if_t<std::is_copy_assignable_v<T>, ccm_counter&>
    {
    T::operator=(other);
    other._stats.copies++;
    _import_stats(other);
    _stats.copy_assign++;
    return *this;
    }

    constexpr auto operator=(ccm_counter&& other) noexcept(std::is_nothrow_move_assignable_v<T>)
    -> std::enable_if_t<std::is_move_assignable_v<T>, ccm_counter&>
    {
    T::operator=(other);
    other._stats.moves++;
    _import_stats(other);
    _stats.move_assign++;
    return *this;
    }

    [[nodiscard]] constexpr ccm_stats get_ccmd_stats() const noexcept
    {
    return _stats;
    }

    constexpr void set_ccmd_stats(ccm_stats stats)
    {
    _stats = std::move(stats);
    }

    private:
    constexpr void _import_stats(const ccm_counter& source)
    {
    _stats.copies = source._stats.copies;
    _stats.moves = source._stats.moves;
    }

    mutable ccm_stats _stats{};
    };


    template <typename T>
    std::ostream& operator<<(std::ostream& os, const ccm_counter<T>& counter)
    {
    return os << counter.get_ccmd_stats();
    }









    share|improve this question
























      up vote
      0
      down vote

      favorite









      up vote
      0
      down vote

      favorite











      I made an object tracker for debugging and testing purposes called ccm_counter (construction, copy, move counter). It counts constructor, copy and move calls. It can be used to detect inefficient forwarding and unnecessary (or unintended) copies. The behavior is as follows:



      What it does



      Let vector<A> be the type you want to test and A its element type (A must be move and copy constructable and must not be final). Drop in the counter like vector<ccm_counter<A>>. ccm_counter behaves like A except that it also counts:




      1. constructor calls

      2. copy constructor calls

      3. move constructor calls

      4. copy assignment calls

      5. move assignment calls

      6. copies

      7. moves


      Point 1 to 5 are bound to the object (or variable) while point 6 and 7 are bound to the value of the object. For example:



      struct A{}; 
      ccm_counter<A> a, b; // a and b increment constructor call count by 1
      b = a; // a +1 copies -> import copies and moves from a -> b +1 copy assignment
      a = b; // b +1 copies -> import copies and moves from b -> a +1 copy assignment
      b = a;


      This example results in (shortened)



      a:
      constructor calls: 1
      copy assignment calls: 1
      total copies made: 3

      b:
      constructor calls: 1
      copy assignment calls: 2
      total copies made: 3


      Now we add the vector<A> (the examples uses std::vector):



      struct A{}; 
      vector<ccm_counter<A>> vec;
      ccm_counter<A> a, b;
      vec.shrink_to_fit();
      vec.push_back(a);
      vec.push_back(b);

      for (int i = 0; i < vec.size(); i++)
      cout << "element " << i << ":n" << vec[i].get_ccmd_stats() << "nn";


      which prints:



      element 0:
      constructor calls: 0
      copy constructor calls: 0
      move constructor calls: 1
      copy assignment calls: 0
      move assignment calls: 0
      total copies made: 1
      total moves made: 1

      element 1:
      constructor calls: 0
      copy constructor calls: 1
      move constructor calls: 0
      copy assignment calls: 0
      move assignment calls: 0
      total copies made: 1
      total moves made: 0


      The constructor and assignment call counts are not very useful in this example, because they are bound to the object itself, which, in this case, is just an element in the internal buffer of the vector (which also changes as soon as the buffer is reallocated). However, the total copies and moves were tracked by the value. Element 0 gets copied when we call vec.push_back(a). Element 1 gets copied when we call vec.push_back(b). Because the vector exceeds his size each time we call vec.push_back(...), its internal buffer has to be reallocated each time. This results in element 0 beeing moved into the new buffer which is reflected in the output.



      Implementation



      #include <type_traits>
      #include <ostream>
      #include <iomanip>
      #include <algorithm>
      #include <cmath>

      struct ccm_stats
      {
      std::size_t ctor = 0; // number of (any, except copy and move) constructor calls
      std::size_t copy_ctor = 0; // number of copy constructor calls
      std::size_t copy_assign = 0; // number of copy assignment calls
      std::size_t move_ctor = 0; // number of move constructor calls
      std::size_t move_assign = 0; // number of move assignment calls
      std::size_t copies = 0; // number of copies made
      std::size_t moves = 0; // number of moves made

      constexpr ccm_stats& operator+=(const ccm_stats& rhs)
      {
      ctor += rhs.ctor;
      copy_ctor += rhs.copy_ctor;
      move_ctor += rhs.move_ctor;
      copy_assign += rhs.copy_assign;
      move_assign += rhs.move_assign;
      copies += rhs.copies;
      moves += rhs.moves;
      return *this;
      }

      constexpr ccm_stats operator+(const ccm_stats& rhs)
      {
      ccm_stats ret(*this);
      return ret += rhs;
      }
      };

      std::ostream& operator<<(std::ostream& os, const ccm_stats& stats)
      {
      using namespace std;
      constexpr size_t sw = 24;
      const size_t nw = static_cast<size_t>(
      log10(max({ stats.ctor, stats.copy_ctor, stats.copy_assign, stats.move_ctor, stats.move_assign, stats.copies, stats.moves }))) + 1;

      os
      << setw(sw) << left << "constructor calls: " << setw(nw) << right << stats.ctor << "n"
      << setw(sw) << left << "copy constructor calls: " << setw(nw) << right << stats.copy_ctor << "n"
      << setw(sw) << left << "move constructor calls: " << setw(nw) << right << stats.move_ctor << "n"
      << setw(sw) << left << "copy assignment calls: " << setw(nw) << right << stats.copy_assign << "n"
      << setw(sw) << left << "move assignment calls: " << setw(nw) << right << stats.move_assign << "n"
      << setw(sw) << left << "total copies made: " << setw(nw) << right << stats.copies << "n"
      << setw(sw) << left << "total moves made: " << setw(nw) << right << stats.moves;
      return os;
      }

      // A wrapper object that inherits from `T` and counts construction, copy and move operations of `T`.
      template <typename T>
      class ccm_counter : public T
      {
      public:
      template <typename ...Args, typename = std::enable_if_t<std::is_constructible_v<T, Args...>>>
      constexpr explicit ccm_counter(Args... args) noexcept(std::is_nothrow_constructible_v<T, Args...>)
      : T(std::forward<Args>(args)...)
      {
      _stats.ctor++;
      }

      constexpr ccm_counter(const ccm_counter& other) noexcept(std::is_nothrow_copy_constructible_v<T>)
      : T(other)
      {
      static_assert(std::is_copy_constructible_v<T>, "T must be copy constructible.");
      other._stats.copies++;
      _import_stats(other);
      _stats.copy_ctor++;
      }

      constexpr ccm_counter(ccm_counter&& other) noexcept(std::is_nothrow_move_constructible_v<T>)
      : T(other)
      {
      static_assert(std::is_move_constructible_v<T>, "T must be move constructible.");
      other._stats.moves++;
      _import_stats(other);
      _stats.move_ctor++;
      }

      constexpr auto operator=(const ccm_counter& other) noexcept(std::is_nothrow_copy_assignable_v<T>)
      -> std::enable_if_t<std::is_copy_assignable_v<T>, ccm_counter&>
      {
      T::operator=(other);
      other._stats.copies++;
      _import_stats(other);
      _stats.copy_assign++;
      return *this;
      }

      constexpr auto operator=(ccm_counter&& other) noexcept(std::is_nothrow_move_assignable_v<T>)
      -> std::enable_if_t<std::is_move_assignable_v<T>, ccm_counter&>
      {
      T::operator=(other);
      other._stats.moves++;
      _import_stats(other);
      _stats.move_assign++;
      return *this;
      }

      [[nodiscard]] constexpr ccm_stats get_ccmd_stats() const noexcept
      {
      return _stats;
      }

      constexpr void set_ccmd_stats(ccm_stats stats)
      {
      _stats = std::move(stats);
      }

      private:
      constexpr void _import_stats(const ccm_counter& source)
      {
      _stats.copies = source._stats.copies;
      _stats.moves = source._stats.moves;
      }

      mutable ccm_stats _stats{};
      };


      template <typename T>
      std::ostream& operator<<(std::ostream& os, const ccm_counter<T>& counter)
      {
      return os << counter.get_ccmd_stats();
      }









      share|improve this question













      I made an object tracker for debugging and testing purposes called ccm_counter (construction, copy, move counter). It counts constructor, copy and move calls. It can be used to detect inefficient forwarding and unnecessary (or unintended) copies. The behavior is as follows:



      What it does



      Let vector<A> be the type you want to test and A its element type (A must be move and copy constructable and must not be final). Drop in the counter like vector<ccm_counter<A>>. ccm_counter behaves like A except that it also counts:




      1. constructor calls

      2. copy constructor calls

      3. move constructor calls

      4. copy assignment calls

      5. move assignment calls

      6. copies

      7. moves


      Point 1 to 5 are bound to the object (or variable) while point 6 and 7 are bound to the value of the object. For example:



      struct A{}; 
      ccm_counter<A> a, b; // a and b increment constructor call count by 1
      b = a; // a +1 copies -> import copies and moves from a -> b +1 copy assignment
      a = b; // b +1 copies -> import copies and moves from b -> a +1 copy assignment
      b = a;


      This example results in (shortened)



      a:
      constructor calls: 1
      copy assignment calls: 1
      total copies made: 3

      b:
      constructor calls: 1
      copy assignment calls: 2
      total copies made: 3


      Now we add the vector<A> (the examples uses std::vector):



      struct A{}; 
      vector<ccm_counter<A>> vec;
      ccm_counter<A> a, b;
      vec.shrink_to_fit();
      vec.push_back(a);
      vec.push_back(b);

      for (int i = 0; i < vec.size(); i++)
      cout << "element " << i << ":n" << vec[i].get_ccmd_stats() << "nn";


      which prints:



      element 0:
      constructor calls: 0
      copy constructor calls: 0
      move constructor calls: 1
      copy assignment calls: 0
      move assignment calls: 0
      total copies made: 1
      total moves made: 1

      element 1:
      constructor calls: 0
      copy constructor calls: 1
      move constructor calls: 0
      copy assignment calls: 0
      move assignment calls: 0
      total copies made: 1
      total moves made: 0


      The constructor and assignment call counts are not very useful in this example, because they are bound to the object itself, which, in this case, is just an element in the internal buffer of the vector (which also changes as soon as the buffer is reallocated). However, the total copies and moves were tracked by the value. Element 0 gets copied when we call vec.push_back(a). Element 1 gets copied when we call vec.push_back(b). Because the vector exceeds his size each time we call vec.push_back(...), its internal buffer has to be reallocated each time. This results in element 0 beeing moved into the new buffer which is reflected in the output.



      Implementation



      #include <type_traits>
      #include <ostream>
      #include <iomanip>
      #include <algorithm>
      #include <cmath>

      struct ccm_stats
      {
      std::size_t ctor = 0; // number of (any, except copy and move) constructor calls
      std::size_t copy_ctor = 0; // number of copy constructor calls
      std::size_t copy_assign = 0; // number of copy assignment calls
      std::size_t move_ctor = 0; // number of move constructor calls
      std::size_t move_assign = 0; // number of move assignment calls
      std::size_t copies = 0; // number of copies made
      std::size_t moves = 0; // number of moves made

      constexpr ccm_stats& operator+=(const ccm_stats& rhs)
      {
      ctor += rhs.ctor;
      copy_ctor += rhs.copy_ctor;
      move_ctor += rhs.move_ctor;
      copy_assign += rhs.copy_assign;
      move_assign += rhs.move_assign;
      copies += rhs.copies;
      moves += rhs.moves;
      return *this;
      }

      constexpr ccm_stats operator+(const ccm_stats& rhs)
      {
      ccm_stats ret(*this);
      return ret += rhs;
      }
      };

      std::ostream& operator<<(std::ostream& os, const ccm_stats& stats)
      {
      using namespace std;
      constexpr size_t sw = 24;
      const size_t nw = static_cast<size_t>(
      log10(max({ stats.ctor, stats.copy_ctor, stats.copy_assign, stats.move_ctor, stats.move_assign, stats.copies, stats.moves }))) + 1;

      os
      << setw(sw) << left << "constructor calls: " << setw(nw) << right << stats.ctor << "n"
      << setw(sw) << left << "copy constructor calls: " << setw(nw) << right << stats.copy_ctor << "n"
      << setw(sw) << left << "move constructor calls: " << setw(nw) << right << stats.move_ctor << "n"
      << setw(sw) << left << "copy assignment calls: " << setw(nw) << right << stats.copy_assign << "n"
      << setw(sw) << left << "move assignment calls: " << setw(nw) << right << stats.move_assign << "n"
      << setw(sw) << left << "total copies made: " << setw(nw) << right << stats.copies << "n"
      << setw(sw) << left << "total moves made: " << setw(nw) << right << stats.moves;
      return os;
      }

      // A wrapper object that inherits from `T` and counts construction, copy and move operations of `T`.
      template <typename T>
      class ccm_counter : public T
      {
      public:
      template <typename ...Args, typename = std::enable_if_t<std::is_constructible_v<T, Args...>>>
      constexpr explicit ccm_counter(Args... args) noexcept(std::is_nothrow_constructible_v<T, Args...>)
      : T(std::forward<Args>(args)...)
      {
      _stats.ctor++;
      }

      constexpr ccm_counter(const ccm_counter& other) noexcept(std::is_nothrow_copy_constructible_v<T>)
      : T(other)
      {
      static_assert(std::is_copy_constructible_v<T>, "T must be copy constructible.");
      other._stats.copies++;
      _import_stats(other);
      _stats.copy_ctor++;
      }

      constexpr ccm_counter(ccm_counter&& other) noexcept(std::is_nothrow_move_constructible_v<T>)
      : T(other)
      {
      static_assert(std::is_move_constructible_v<T>, "T must be move constructible.");
      other._stats.moves++;
      _import_stats(other);
      _stats.move_ctor++;
      }

      constexpr auto operator=(const ccm_counter& other) noexcept(std::is_nothrow_copy_assignable_v<T>)
      -> std::enable_if_t<std::is_copy_assignable_v<T>, ccm_counter&>
      {
      T::operator=(other);
      other._stats.copies++;
      _import_stats(other);
      _stats.copy_assign++;
      return *this;
      }

      constexpr auto operator=(ccm_counter&& other) noexcept(std::is_nothrow_move_assignable_v<T>)
      -> std::enable_if_t<std::is_move_assignable_v<T>, ccm_counter&>
      {
      T::operator=(other);
      other._stats.moves++;
      _import_stats(other);
      _stats.move_assign++;
      return *this;
      }

      [[nodiscard]] constexpr ccm_stats get_ccmd_stats() const noexcept
      {
      return _stats;
      }

      constexpr void set_ccmd_stats(ccm_stats stats)
      {
      _stats = std::move(stats);
      }

      private:
      constexpr void _import_stats(const ccm_counter& source)
      {
      _stats.copies = source._stats.copies;
      _stats.moves = source._stats.moves;
      }

      mutable ccm_stats _stats{};
      };


      template <typename T>
      std::ostream& operator<<(std::ostream& os, const ccm_counter<T>& counter)
      {
      return os << counter.get_ccmd_stats();
      }






      c++ c++17






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked 22 mins ago









      Timo

      1818




      1818



























          active

          oldest

          votes











          Your Answer





          StackExchange.ifUsing("editor", function () {
          return StackExchange.using("mathjaxEditing", function () {
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          });
          });
          }, "mathjax-editing");

          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "196"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: false,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210011%2ftracker-for-object-construction-copy-and-movement%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown






























          active

          oldest

          votes













          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes
















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Code Review Stack Exchange!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          Use MathJax to format equations. MathJax reference.


          To learn more, see our tips on writing great answers.





          Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


          Please pay close attention to the following guidance:


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210011%2ftracker-for-object-construction-copy-and-movement%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Create new schema in PostgreSQL using DBeaver

          Deepest pit of an array with Javascript: test on Codility

          Costa Masnaga