Showing posts with label cpp_unique_ptr. Show all posts
Showing posts with label cpp_unique_ptr. Show all posts

Dec 31, 2023

[Cppcon 2023] Expressing Implementation Sameness and Similarity - Polymorphism in Modern C++, Daisy Hollman

Reference:
https://youtu.be/Fhw43xofyfo?si=PcMFEsqb7NRiCmyX

Two different kinds of sameness

Interface sameness

  • Enables users to treat things the same way in their code(create their own sameness)
  • Cannot be removed later
    • You can't change your user's code(at least not easily)
    • Once you allow users to treat things as the same, it's very hard to change that later
  • When done well: low code coupling(the degree of interdependence between software modules)

Implementation sameness

  • Enables readers to understand and use existing sameness of similarity
  • Can be changed or removed at any point in the future when it stops being helpful
  • When done well: high code cohesion(the degree to which the elements inside a module belong together)
Let this concept sinks in.

Consider about 'type of T'; e.g. template<typename T> vector{};


Reusing customization points.

Mixin mixins.

template<class> struct CRTPMixin;
template<template <class> class Mixin, class Derived>
struct CRTPMixin<Mixin<Derived>> {
  consteval auto& self() { return static_cast<Derived&>(*this); }
  consteval auto const& self() const { return static_cast<Derived const&>(*this); }
};

template<typename Derived>
struct PrintableElementwise : CRTPMixin<PrintableElementwise<Derived>> {
 void print() const {
    auto const& e = self().elements();
    std::apply([](auto const&... el) {
      ([&]{ cout << el << "\n"; }(), ...);
      }, e);
    }
};


As stated by Sy Brand's C++23’s Deducing this can help with the above

https://devblogs.microsoft.com/cppblog/cpp23-deducing-this/
static_cast<Derived const&>(*this); 
part. But really? Consider code uses deduce this from C++23:
struct PrintableElementwise {
  void print(this auto const& self) {
    auto const& e = self.elements();
    std::apply([](auto const&... el) {
      ([&]{ count << el << "\n"; }(), ...);
    }, e);
  }
};

struct Foo : PrintableElementwise {
 int a;
 double b;
 std::string s;
 auto elements() const {
    return std::forward_as_tuple(x, y, z);
 }
};
Above has mixed interface sameness with implementation sameness; which causes error:
auto items = std::vector<PrintableElementwise>{/*...*/};
for (auto* i : items) {
    i->print(); // error if derived type has no memfn elements().
}
Use the idea of 'type of T'. Which indicates the type's embedded behavior.


Qualifier forwarding

e.g.
Perfect forwarding:
template<class T>
void foo(T&& t) {
    bar(std::forward<T>(t));
}
equivalent to:
template<class T>
void foo(T& t) {
 bar(t);
}

template<class T>
using not_forwarding_ref = T;

template<class T>
void foo(not_forwarding_ref<T>&& t) {
  bar(std::move(t));
}


Don't Repeat Yourself(DRY)

'Every piece of knowledge must have a single, unambiguous, authoritative representation within a system


Separating the ownership mechanism (typical pattern for a class template customization point

From:

template<class Thing>
class OwingCollection {
 private:
   vector<unique_ptr<Thing>> things_;
 protected:
   void for_each(/* concept */ std::invocable<Thing const&> auto&& f) const { /*...*/};
 public:
   void insert(unique_ptr<Thing>) {};
   unique_ptr<Thing> remove(unique_ptr<Thing>) {};
   bool contains(unique_ptr<Thing>) const {};
   void remove_if(invocable<Thing const&> auto&&) {};
};
To:
template<class Thing, template<class...> class Owner = unique_ptr>
class OwingCollection {
 private:
   vector<Owner<Thing>> things_;
 protected:
   void for_each(/* concept */ std::invocable<Thing const&> auto&& f) const { /*...*/};
 public:
   void insert(Owner <Thing>) {};
   Owner <Thing> remove(Owner <Thing>) {};
   bool contains(Owner <Thing>) const {};
   void remove_if(invocable<Thing const&> auto&&) {};
};


STD's example of separable pattern 

[C++14] unique_ptr with type erasure as shared_ptr 


C++20 Concepts

C++20 concepts extract interface sameness without your permission.

Concepts allow library users to accidentally create code coupling between unrelated modules based only on names.
e.g.
template<class T>
  requires requires(T&& t) { { t.clear() } -> convertible_to<bool>; }
void do_the_stuff(T&& t) { /*...*/ }
Those two types fit for above API. 
However; their meaning of memfn clear() is different.
struct Container {
  // returns true if the container was
  // non-empty before the clear
  bool clear();
};

struct Color {
  // returns true if opacity == 0
  bool clear();
};
Concepts does not differentiate namespace.


More places having the concept of 'sameness'

  • 'Normal' functions (C like)
  • Macros and Code generation
    • 'Gross' but sometimes better than repeating things.
  • Customization Point Objects(CPOs)
  • Type erasure
  • constexpr functions
    • 'Sameness' of compile-time and runtime implementations.
  • Dependency injection (needs reflection)
  • Aspect-oriented programming(needs reflection)
  • Decoration(needs reflection)

Jan 26, 2021

[C++] trivial_abi and borrow ownership

An interesting concept comes up from Rust's borrow ownership.

In C++, the unique_ptr can only be moved. While instead of using heavy-weighted shared_ptr
(i.e 2 words size, heap allocated control block, atomic ref-counting, weak_ptr etc.), the only way of borrow the ownership from unique_ptr is by passing raw pointer from unique_ptr.

Here's the tweet from Prof. John Regehr talking about this:
https://twitter.com/johnregehr/status/1351333748277592065
Follow up from s.o:
https://stackoverflow.com/a/30013891
i.e in C++, the borrow concept can't be made in compile time, but in runtime, which unlike Rust.

Another to mention about is trivial_abi.
AMD64 ABI for C++: https://www.uclibc.org/docs/psABI-x86_64.pdf

While call by value,

Quote from ABI:
If a C++ object has either a non-trivial copy constructor or a non-trivial destructor, it is passed by invisible reference (the object is replaced in the parameter list by a pointer […]).

And in Itanium C++ ABI document, quote:

non-trivial for the purposes of calls
A type is considered non-trivial for the purposes of calls if: it has a non-trivial copy constructor, move constructor, or destructor, or all of its copy and move constructors are deleted.
This definition, as applied to class types, is intended to be the complement of the definition in [class.temporary]p3 of types for which an extra temporary is allowed when passing or returning a type. A type which is trivial for the purposes of the ABI will be passed and returned according to the rules of the base C ABI, e.g. in registers; often this has the effect of performing a trivial copy of the type.

That is call by value with non-trivial type introduce double indirection during the call while the type could potentially not fit into the register for callee.  By double indirection meaning the argument is reference to the temporary r-value created on caller's stack.
i.e potential virtual pointer to v-table increases the size of type.


Reference: https://www.raywenderlich.com/615-assembly-register-calling-convention-tutorial

Assembly code can be found in godbolt:
https://godbolt.org/z/s1TPea6sx


#include <cstdio>

#define TRIVIAL_ABI __attribute__((trivial_abi))

template <class T> T incr(T obj) {
  obj.value += 1;
  puts("before exit incr func");
  return obj;
}

struct Up1 {
  int value;
  Up1() = default;
  Up1(const Up1& u) : value(u.value) { puts("Up1 copy constructor"); }
  ~Up1() { printf("detroyed Up1 value: %d\n", value); }
};

struct TRIVIAL_ABI Up2 {
  int value;
  Up2() = default;
  Up2(const Up1& u) : value(u.value) { puts("Up2 copy constructor"); }
  ~Up2() { printf("detroyed Up2 value: %d\n", value); }
};

template Up1 incr(Up1);
template Up2 incr(Up2);

auto main() -> int {
  auto u1 = Up1{};
  puts("before call incr func for u1");
  incr(u1);

  printf("\n\n");

  auto u2 = Up2{};
  puts("before call incr func for u2");
  incr(u2);
}

Output:
before call incr func for u1
Up1 copy constructor; temporary object creatd;
before exit incr func
Up1 copy constructor; temporary object creatd;
detroyed Up1 value: 1
detroyed Up1 value: 1


before call incr func for u2
// No temporary object created, all in register.
before exit incr func
detroyed Up2 value: 1
detroyed Up2 value: 1
detroyed Up2 value: 0
detroyed Up1 value: 0

Jun 21, 2017

[C++] Always consider function thread safeness drags performance in single thread code.

std::cin / std::cout  are thread safe according to CPP ISO (30.2.3 Thread safety),
thus means it's slow in single thread.

Use std::fstream instead.

std::shared_ptr is thread safe for internal ref counting.
That is to say, using std::shared_ptr is slow in single thread as well.
Use _move_ for std::shared_ptr or just using std::unique_ptr.


When running single threaded code, for performance, always think
potential function call is thread safe or not, thus could impact performance.

Ref:

code:

#ifdef __GLIBCXX__
template<typename T>
  using single_threaded_shared_ptr = std::__shared_ptr<T, std::_S_single>;
#else
template<typename T>
  using single_threaded_shared_ptr = std::shared_ptr<T>;
#endif

auto p = std::__make_shared<T, std::_S_single>(args...);

Reference:
https://stackoverflow.com/questions/32317370/avoid-cost-of-stdmutex-when-not-multi-threading
https://stackoverflow.com/questions/3652056/how-efficient-is-locking-an-unlocked-mutex-what-is-the-cost-of-a-mutex
Why is the std::function () operator const?

Sep 30, 2016

[cppcon2016][c++] Leak-Freedom in C++ - note

CppCon 2016 - Herb Sutter “Leak-Freedom in C++... By Default.”-
https://goo.gl/2TNgQI
--
  • Ensure an object will be destroyed once it is no longer needed.
  • Correct by construction!
template<typename T>
using Pimpl = const unique_ptr<T>;
unique_ptr<data[]> ptr;


Double linked list:
  • a unique_ptr to the next
  • a raw ptr to the back
shared_ptr with alias constructor:
  • Do not violate layering.
  • Don't create ownership cycles across modules by owning 'upward' (violates layering)
  • Use weak_ptr to break cycles.

How?
  • Don't pass an owner down to 'unknow code' that might store it.
    e.g storing a shared_ptr
  • Simple.
    Use weak_ptr inside the call back callable object which point
    to outside resource.
ownership types:
  • 1 object, 1 owner:
    unique_ptr
  • 1 object , n owners:
    shared_ptr
  • N objects, 1 or n owners:
    deffered_ptr
Reachability is a property of the while group.
Not detectable from 1 object, or subgroup.

Idea:
heap for deffered_ptr is isolated.
Each module could have one isolated heap.
github.com/hsutter/gcpp

Dec 17, 2014

[C++14] unique_ptr with type erasure as shared_ptr

Extra information:
Non-capturing C++ lambdas can be converted to a pointer to function, but what about the calling convention?

Wrapper for unique_ptr to have the same behavior as shared_ptr when dealing with derived types. i.e type erasure.
#include <iostream>
#include <memory>
#include <utility>
using namespace std;
template<typename T, typename U>
auto GetUniquePtr(U* ptr)
{
   auto deleter = [](auto ptr)
   {
       delete (U*)(ptr);
   };
  //RVO enabled.
   return unique_ptr<T, decltype(deleter)>{ptr, deleter}; 
   // use decltype(deleter) instead of void(*)(T*) for compiler
   // to deduce type
   /*return unique_ptr<T, void(*)(T*)>{ptr, deleter};
       using void(*)(T*) will force internal tuple to store a
       pointer to function , which is 1 word size! */
}
struct Base_1
{
   void ha()
   {
       cout << "coding is for fun!" << endl;
   }
   ~Base_1()
   {
       cout << "Base_1 destructor" << endl;
   }
};
struct Base_2 : Base_1
{
   ~Base_2()
   {
       cout << "Base_2 destructor" << endl;
   }
};
 
int main()
{
   auto u_ptr = GetUniquePtr<Base_1>(new Base_2);
   cout << sizeof(u_ptr) << endl; //same size as default deleter.
   u_ptr->ha();
}