2022-02-16 00:53:01 -05:00
---
title:
2022-02-18 15:59:12 -05:00
C++ Automatic Memory Management
2022-05-06 22:49:33 -04:00
...
2024-06-28 03:52:41 -04:00
2022-02-16 00:53:01 -05:00
# Memory Safety
2024-06-28 03:52:41 -04:00
2022-02-16 00:53:01 -05:00
Modern, mostly memory safe C++, is enforced by:\
- Microsoft safety checker
- Guidelines
- language checker
`$ clang-tidy test.cpp -checks=clang-analyzer-cplusplus*, cppcoreguidelines-*, modernize-*` will catch most of the issues that esr
complains about, in practice usually all of them, though I suppose that as
the project gets bigger, some will slip through.
2022-02-18 15:59:12 -05:00
static_assert(__cplusplus >= 201703, "C version of out of date");
2022-02-16 00:53:01 -05:00
2022-02-22 01:56:03 -05:00
Adds the std::span type, which makes pointer handling a whole lot simpler and safer.
The size of the array pointed to is kept with the pointer for safe iteration and bounds checking during pointer maths. Also, translates std::array and old type C arrays to the same type, which makes life much simpler and safer. You get all the new good range stuff from both of them.
2022-02-16 00:53:01 -05:00
Modern C++ as handles arrays as arrays where possible, but they quickly
decay to pointers – which you avoid using spans. std::array is a C array
whose size is known at compile time, and which is protected from decay to
a pointer. std::vector is a dynamically resizable and insertable array
protected from decay to a pointer – which can have significant overheads.
std::make_unique, std::make_shared create pointers to memory managed
objects. (But single objects, not an array, use spans for pointer
arithmetic)
2022-02-18 15:59:12 -05:00
auto sp = std::make_shared< int > (42);
std::weak_ptr< T > wp{sp};
2022-02-16 00:53:01 -05:00
# Array sizing and allocation
2022-02-18 15:59:12 -05:00
/* This code creates a bunch of "brown dog" strings on the heap to test automatic memory management. */
char ca[]{ "red dog" }; //Automatic array sizing
std::array< char , 8 > arr{"red dog"}; //Requires #include < array >
/* No automatic array sizing, going to have to count your initializer list. */
/* The pointer of the underlying array is referenced by & arr[0] but arr is not the underlying array, nor a pointer to it. */
/* [0] invokes operator[], and operator[] is the member function that accesses the underlying array.*/
/* The size of the underlying array is referenced by arr.size();*/
/* size known at compile time, array can be returned from a function getting the benefits of stack allocation.*/
// can be passed around like POD
char *p = new char[10]{ "brown dog" }; //No automatic array
// sizing for new
std::unique_ptr< char [ ] > puc{ p }; // Now you do not have
// to remember to delete p
auto puc2 = std::move(puc); /* No copy constructor. Pass by reference, or pass a view, such as a span.*/
std::unique_ptr< char > puc3{ new char[10]{ "brown dog" } };
/* Array size unknown at compile or run time, needs a span, and you have to manually count the initialization list. */
/* Compiler guards against overflow, but does not default to the correct size.*/
/* You can just guess a way too small size, and the compiler in its error message will tell you what the size should be. */
auto pu = std::make_unique< char [ ] > (10); // uninitialized,
// needs procedural initialization.
/* span can be trivially created from a compile time declared array, an std:array or from a run time std:: vector, but then these things already have the characteristics of a span, and they own their own storage. */
/* You would use a span to point into an array, for example a large blob containing smaller blobs.*/
// Placement New:
char *buf = new char[1000]; //pre-allocated buffer
char *p = buf;
MyObject *pMyObject = new (p) MyObject();
p += (sizeof(MyObject+7)/8)*8
/* Problem is that you will have to explictly call the destructor on each object before freeing your buffer. */
/* If your objects are POD plus code for operating on POD, you don’ t have to worry about destructors.*/
// A POD object cannot do run time polymorphism.
/* The pointer referencing it has to be of the correct compile time type, and it has to explicitly have the default constructor when constructed with no arguments.*/
/* If, however, you are building a tree in the pre-allocated buffer, no sweat. */
/* You just destruct the root of the tree, and it recursively destructs all its children. */
/* If you want an arbitrary graph, just make sure you have owning and non owning pointers, and the owning pointers form a tree. */
/* Anything you can do with run time polymorphism, you can likely do with a type flag.*/
static_assert ( std::is_pod< MyType > () , "MyType for some reason is not POD" );
class MyClass
{
public:
MyClass()=default; // Otherwise unlikely to be POD
MyClass& operator=(const MyClass& ) = default; // default assignment Not actually needed, but just a reminder.
};
### alignment
```c++
// every object of type struct_float will be aligned to alignof(float) boundary
2022-02-16 00:53:01 -05:00
// (usually 4)
struct alignas(float) struct_float {
2022-02-18 15:59:12 -05:00
// your definition here
2022-02-16 00:53:01 -05:00
};
2022-02-18 15:59:12 -05:00
2022-02-16 00:53:01 -05:00
// every object of type sse_t will be aligned to 256-byte boundary
struct alignas(256) sse_t
{
2022-02-18 15:59:12 -05:00
float sse_data[4];
2022-02-16 00:53:01 -05:00
};
2022-02-18 15:59:12 -05:00
2022-02-16 00:53:01 -05:00
// the array "cacheline" will be aligned to 128-byte boundary
alignas(128) char cacheline[128];
2022-02-18 15:59:12 -05:00
```
2022-02-16 00:53:01 -05:00
# Construction, assignment, and destruction
six things: ([default
constructor](https://en.cppreference.com/w/cpp/language/default_constructor),
[copy
constructor](https://en.cppreference.com/w/cpp/language/copy_constructor),
[move
constructor](https://en.cppreference.com/w/cpp/language/move_constructor),
[copy
assignment](https://en.cppreference.com/w/cpp/language/copy_assignment),
[move
assignment](https://en.cppreference.com/w/cpp/language/move_assignment)
and [destructor ](https://en.cppreference.com/w/cpp/language/destructor ))
are generated by default – except when they are not.
So it is arguably a good idea to explicitly declare them as default or
deleted.
Copy constructors
2022-02-18 15:59:12 -05:00
A(const A& a)
2022-02-16 00:53:01 -05:00
Copy assignment
2022-02-18 15:59:12 -05:00
A& operator=(const A other)
2022-02-16 00:53:01 -05:00
Move constructors
2022-02-18 15:59:12 -05:00
class_name ( class_name & & other)
A(A& & o)
D(D& & ) = default;
2022-02-16 00:53:01 -05:00
Move assignment operator
2022-02-18 15:59:12 -05:00
V& operator=(V& & other)
2022-02-16 00:53:01 -05:00
Move constructors
2022-02-18 15:59:12 -05:00
class_name ( class_name & & )
2022-02-16 00:53:01 -05:00
## rvalue references
Move constructors and copy constructors primarily exist to tell the
compiler how to handle temporary values, rvalues, that have references to possibly
costly resources.
`class_name&&` is rvalue reference, the canonical example being a reference to a compiler generated temporary.
The primary purpose of rvalue references is to support move semantics in
objects that reference resources, primarily unique_pointer.
`std::move(t)` is equivalent to `static_cast<decltype(t)&&>(t)` , causing move
semantics to be generated by the compiler.
2024-06-28 03:52:41 -04:00
`t` , the compiler assumes, is converted by your move constructor or move assignment into a valid state where your destructor will not need to anything very costly.
2022-02-16 00:53:01 -05:00
`std::forward(t)` causes move semantics to be invoked iff the thing referenced
is an rvalue, typically a compiler generated temporary, *conditionally*
forwarding the resources.
where `std::forward` is defined as follows:
2022-02-18 15:59:12 -05:00
template< class T > struct remove_reference {
typedef T type;
};
template< class T > struct remove_reference< T & > {
typedef T type;
};
template< class T > struct remove_reference< T & & > {
typedef T type;
};
template< class S >
S& & forward(typename std::remove_reference< S > ::type& a) noexcept
{
return static_cast< S & & > (a);
}
2022-02-16 00:53:01 -05:00
`std::move(t)` and `std::forward(t)` don't actually perform any action
in themselves, rather they cause the code referencing `t` to use the intended
copy and intended assignment.
## constructors and destructors
If you declare the destructor deleted that prevents the compiler from
generating its own, possibly disastrous, destructor, but then, of
course, you have to define your own destructor with the exact same
signature, which would ordinarily stop the compiler from doing that
anyway.
When you declare your own constructors, copiers, movers, and deleters,
you should generally mark them noexcept.
2022-02-18 15:59:12 -05:00
struct foo {
foo() noexcept {}
foo( const foo & ) noexcept { }
foo( foo & & ) noexcept { }
~foo() {}
};
2022-02-16 00:53:01 -05:00
Destructors are noexcept by default. If a destructor throws an exception as
a result of a destruction caused by an exception, the result is undefined,
and usually very bad. This problem is resolved in complicated ad hoc
ways that are unlikely to be satisfactory.
If you need to define a copy constructor, probably also need to define
an assignment operator.
2022-02-18 15:59:12 -05:00
t2 = t1; /* calls assignment operator, same as "t2.operator=(t1);" */
Test t3 = t1; /* calls copy constructor, same as "Test t3(t1);" */
2022-02-16 00:53:01 -05:00
## casts
You probably also want casts. The surprise thing about a cast operator
is that its return type is not declared, nor permitted to be declared,
DRY. Operator casts are the same thing as constructors, except declared
in the source class instead of the destination class, hence most useful
when you are converting to a generic C type, or to the type of an
external library that you do not want to change.
2022-02-18 15:59:12 -05:00
struct X {
int y;
operator int(){ return y; }
operator const int& (){ return y; } /* C habits would lead you to incorrectly expect "return &y; ", which is what is implied under the hood. */
operator int*(){ return &y; } // Hood is opened.
};
2022-02-16 00:53:01 -05:00
Mpir, the Visual Studio skew of GMP infinite precision library, has some
useful and ingenious template code for converting C type functions of
the form `SetAtoBplusC(void * a, void * b, void * c);` into C++
expressions of the form `a = b+c*d;` . It has a bunch of intermediate
types with no real existence, `__gmp_expr<>` and `__gmp_binary_expr<>`
and methods with no real existence, which generate the appropriate
calls, a templated function of potentially unlimited complexity, to
convert such an expression into the relevant C type calls using
pointers. See section mpir-3.0.0.pdf, section 17.5 “C++ Internals”.
I don’ t understand the Mpir code, but I think what is happening is that
at run time, the binary expression operating on two base types creates a
transient object on the stack containing pointers to the two base types,
and the assignment operator and copy create operator then call the
appropriate C code, and the operator for entities of indefinite
complexity creates base type values on the stack and a binary expression
operator pointing to them.
Simpler, but introducing a redundant copy, to always generate
intermediate values on the stack, since we have fixed length objects
that do not need dynamic heap memory allocation, not that costly, and
they are not that big, at worst thirty two bytes, so clever code is apt
to cost in overheads of pointer management
That just means we are putting 256 bits of intermediate data on the
stack instead of 128, hardly a cost worth worrying about. And in the
common bad case, (a+b)\*(c+d) clever coding would only save one stack
allocation and redundant copy.
# Template specialization
2022-02-18 15:59:12 -05:00
namespace N {
template< class T > class Y { /*...*/ }; // primary template
template< > class Y< double > ; // forward declare specialization for double
}
template< >
class N::Y< double > { /*...*/ }; // OK: specialization in same namespace
2022-02-16 00:53:01 -05:00
is used when you have sophisticated template code, because you have to
use recursion for looping as the Mpir system uses it to evaluate an
arbitrarily complex recursive expression – but I think my rather crude
implementation will not be nearly so clever.
2022-02-18 15:59:12 -05:00
extern template int fun(int);
/*prevents redundant instantiation of fun in this compilation unit – and thus renders the code for fun unnecessary in this compilation unit.*/
2022-02-16 00:53:01 -05:00
# Template traits, introspection
Template traits: C++ has no syntactic sugar to ensure that your template
is only called using the classes you intend it to be called with.
Often you want different templates for classes that implement similar functionality in different ways.
This is the entire very large topic of template time, compile time code, which is a whole new ball of wax that needs to be dealt with elsewhere
# Abstract and virtual
An abstract base class is a base class that contains a pure virtual
function ` virtual void features() = 0;` .
A class can have a virtual destructor, but not a virtual constructor.
If a class contains virtual functions, then the default constructor has
to initialize the pointer to the vtable. Otherwise, the default
constructor for a POD class is empty, which implies that the default
destructor is empty.
The copy and swap copy assignment operator, a rather slow and elaborate
method of guaranteeing that an exception will leave the system in a good
state, is never generated by default, since it always relates to rather
clever RAII.
An interface class is a class that has no member variables, and where
all of the functions are pure virtual! In other words, the class is
purely a definition, and has no actual implementation. Interfaces are
useful when you want to define the functionality that derived classes
must implement, but leave the details of how the derived class
implements that functionality entirely up to the derived class.
Interface classes are often named beginning with an I. Here’ s a sample
interface class:.
2022-02-18 15:59:12 -05:00
class IErrorLog
{
public:
virtual bool openLog(const char *filename) = 0;
virtual bool closeLog() = 0;
2022-02-16 00:53:01 -05:00
2022-02-18 15:59:12 -05:00
virtual bool writeError(const char *errorMessage) = 0;
2022-02-16 00:53:01 -05:00
2022-02-18 15:59:12 -05:00
virtual ~IErrorLog() {} // make a virtual destructor in case we delete an IErrorLog pointer, so the proper derived destructor is called
// Notice that the virtual destructor is declared to be trivial, but not declared =0;
};
2022-02-16 00:53:01 -05:00
[Override
specifier](https://en.cppreference.com/w/cpp/language/override)
2022-02-18 15:59:12 -05:00
struct A
{
virtual void foo();
void bar();
};
2022-02-16 00:53:01 -05:00
2022-02-18 15:59:12 -05:00
struct B : A
{
void foo() const override; // Error: B::foo does not override A::foo
// (signature mismatch)
void foo() override; // OK: B::foo overrides A::foo
void bar() override; // Error: A::bar is not virtual
};
2022-02-16 00:53:01 -05:00
Similarly [Final
specifier](https://en.cppreference.com/w/cpp/language/final)
[To obtain aligned
storage](http://www.cplusplus.com/reference/type_traits/aligned_storage/)for
use with placement new
2022-02-18 15:59:12 -05:00
void* p = aligned_alloc(sizeof(NotMyClass));
MyClass* pmc = new (p) MyClass; //Placement new.
// ...
pmc->~MyClass(); //Explicit call to destructor.
aligned_free(p);.
2022-02-16 00:53:01 -05:00
2024-06-28 03:52:41 -04:00
# spans
2022-02-16 00:53:01 -05:00
2024-06-28 03:52:41 -04:00
A span is a non owning pointer and count.
2022-02-16 00:53:01 -05:00
2024-06-28 03:52:41 -04:00
It would perhaps be more useful if we also hand owning pointers with counts
2022-02-16 00:53:01 -05:00
# The Curiously Recurring Template Pattern
[CRTP ](https://www.fluentcpp.com/2017/05/16/what-the-crtp-brings-to-code/ ),
makes the relationship between the templated base class or classes and
the derived class cyclic, so that the derived class tends to function as
real base class. Useful for mixin classes.
2022-02-18 15:59:12 -05:00
template < typename T > class Mixin1{
public:
// ...
void doSomething() //using the other mixin classes and the derived class T
{
T& derived = static_cast< T & > (*this);
// use derived...
}
private:
mixin1(){}; // prevents the class from being used outside the mix)
friend T;
};
template < typename T > class Mixin2{
{
public:
// ...
void doSomethingElse()
{
T& derived = static_cast< T & > (*this);
// use derived...
}
private:
Mixin2(){};
friend T;
};
class composite: public mixin1< composite > , public mixin2< composite > {
composite( int x, char * y): mixin1(x), mixin2(y[0]) { ...}
composite():composite(7,"a" ){ ...}
}
2022-02-16 00:53:01 -05:00
# Aggregate initialization
A class of aggregate type has no constructors – the aggregate
constructor is implied default.
A class can be explicitly defined to take aggregate initialization
2022-02-18 15:59:12 -05:00
Class T{
T(std::initializer_list< const unsigned char > in){
for (auto i{in.begin); i< in.end ( ) ; i + + ) {
do stuff with i
}
}
2022-02-16 00:53:01 -05:00
2023-12-23 22:39:39 -05:00
but that does not make it of aggregate type.
Aggregate type has *no* constructors
except default and deleted constructors
2022-02-16 00:53:01 -05:00
# functional programming
2023-12-23 22:39:39 -05:00
A lambda is a nameless value of a nameless class that is a
functor, which is to say, has `operator()` defined.
2024-07-03 20:18:48 -04:00
That each lambda is a unique and nameless class allows the compiler
to optimise it away, so that it becomes inline code.
So doing the kind of stuff with a lambda you would ordinarily need
a class name to do is likely to break stuff or lead to weirdness.
It looks like you are defining a real class, but your compiler does
not want to generate a real class except as a last resort.
2023-12-23 22:39:39 -05:00
But, of course you can get the class with `decltype`
and assign that nameless value to an `auto` variable,
2024-07-03 20:18:48 -04:00
and all that, but because the design for lambdas was
to allow them to be efficiently converted to straightforward
code this may well result in strange complications and mysterious
syntax and semantic errors.
To tell the compiler to actually use the lambda as a lambda, and
not do all this behind the scenes cleverness, you use `std::function`
which is a regular templated class that defines `operator ()` , and if
it is defined in terms of lambda, stashes that lambda on the heap
after the style of `std::string`
However for very small lambdas (not capturing any variables, or capturing
on one variably by value, this can get optimised away), and under
the hood it does things C style.
However, depending on the compiler, `std::function` may do hidden heap
allocation.
2023-12-23 22:39:39 -05:00
But if you are doing all that, might as well explicitly define a
named functor class.
2024-07-03 20:18:48 -04:00
## lambda on the heap
2022-02-16 00:53:01 -05:00
To construct a lambda in the heap:
2024-06-28 03:52:41 -04:00
```c++
auto p = new auto([a,b,c](){})
auto q = new auto([](int x) { return x * x; });
int result = (*q)(5); // Calls the lambda with the argument 5
delete p;
delete q;
```
2022-02-16 00:53:01 -05:00
2024-06-28 03:52:41 -04:00
But if you have pointers to lambdas, std::function is more useful than lambda, because then you can declare their pointer type.
2022-02-16 00:53:01 -05:00
2024-06-28 03:52:41 -04:00
If you want to declare a pointer type that can point to any lambda with the same signature, or indeed any instance of
a class that supports operator(), you can use std::function:
2022-02-16 00:53:01 -05:00
2024-06-28 03:52:41 -04:00
```C++
#include <functional>
2022-02-16 00:53:01 -05:00
2024-06-28 03:52:41 -04:00
// Declare a pointer to a lambda that takes an int and returns an int
std::function< int ( int ) > * lambdaPtr = new std::function< int ( int ) > ([](int x) { return x * x; });
2022-02-16 00:53:01 -05:00
2024-06-28 03:52:41 -04:00
// Use the lambda
int result = (*lambdaPtr)(5);
2022-02-16 00:53:01 -05:00
2024-06-28 03:52:41 -04:00
// Clean up
delete lambdaPtr;
```
2022-02-16 00:53:01 -05:00
2024-06-28 03:52:41 -04:00
This way, lambdaPtr is a pointer to a std::function that can store any callable object, including lambdas, that take an int and return an int.
2022-02-16 00:53:01 -05:00
2024-06-28 03:52:41 -04:00
similarly placement `new` , and `unique_ptr` .
2022-02-16 00:53:01 -05:00
2024-07-03 20:18:48 -04:00
Trouble is that an std::function object is a fixed sized object, like an `std::string` , typically sixteen bytes. which like an `std::string` points to a dynamically allocated object on the heap.
2024-06-28 03:52:41 -04:00
## callbacks
2022-02-16 00:53:01 -05:00
2024-07-03 20:18:48 -04:00
In C, a callback is implemented as an ordinary function pointer, and a pointer to void,
which is then cast to a data structure of the desired type.
What the heavy C++ machinery of `std::function` does is bind the two together and then
do memory management after the fashion of `std::string` .
[compiler explorer]:https://godbolt.org/ {target="_blank"}
And `std::function` , used correctly, should compile to the identical code
merely wrapping the function pointer and the void pointer in a single struct
-- but you had better use [compiler explorer] to make sure
that you are using it correctly.
2022-02-16 00:53:01 -05:00
2024-07-03 20:18:48 -04:00
Write a callback in C, an an std::function in c++, and make sure that
the compiler generates what it should.
Ownership is going to be complicated -- since after createing and passing a callback, we
probably do not want ownership any more -- the thread is going to return
and be applied to some entirely different task. So the call that is passed the callback
as an argument by reference uses `move` to ensure that when the `std::function`
stack value in its caller pointing to the heap gets destroyed, it does not
free the value on the heap, and then stashes the moved `std::function` in some
safe place.
Another issue is that rust, python, and all the rest cannot talk to C++, they can only
talk C. On the other hand, the compiler will probably optimise the `std::function` that
consists of a lamda that is a call to function pointer and that captures a pointer to void.
Again, since compiler is designed for arcane optimization issues, have to see what happens
in [compiler explorer].
But rather than guessing about the compiler correctly guessing intent, make the
callback a C union type implementing std variant in C, being a union of `std:monostate`
a C callback taking no arguments, a C++ callback taking no arguments, C and C++ callbacks
taking a void pointer argument, a c++ callback that is a pointer to method, and
a C++ callback that is an `std::function`
In the old win32 apis, which were designed for C, and then retrofitted for C++
they would have a static member function that took an LPARAM, which was a pointer
to void pointing at the actual object, and then the static member function
would directly call the appropriate, usually virtual, actual member function.
Member function pointers have syntax that no one can wrap their brains around
so people wrap them in layers of typedefs.
2022-02-16 00:53:01 -05:00
2024-06-28 03:52:41 -04:00
Sometimes you want to have indefinitely many data structures, which are dynamically allocated
and then discarded.
2022-02-16 00:53:01 -05:00
2024-06-28 03:52:41 -04:00
Sometimes you want to have a single data structure that gets overwritten frequently. The latter is
preferable when it suffices, since it means that asynch callback code is more like sync code.
2022-02-16 00:53:01 -05:00
2024-06-28 03:52:41 -04:00
In one case, you would allocate the object every time, and when does with it, discard it.
2022-02-16 00:53:01 -05:00
2024-06-28 03:52:41 -04:00
In the other case it would be a member variable of struct that hangs around and is continually
re-used.
2022-02-16 00:53:01 -05:00
2024-07-03 20:18:48 -04:00
### C compatibility
Bind the two together in a way that C can understand:
The code that calls the callback knows nothing about how the blob is structured.
The event management code knows nothing about how the blob is structured.
But the function pointer in the blob *does* know how the blob is structured.
```C
// p points to or into a blob of data containing a pointer to the callback
// and the data that the callback needs is in a position relative to the pointer
// that is known to the callback function.
enum event_type { monovalue, reply, timeout, unreachable, unexpected_error };
struct callback;
typedef extern "C" void (*callback_)(callback * pp, event_type evtype, void* event);
struct callback
{
callback_ p;
};
callback * pp;
// Within the actual function in the event handling code,
// one has to cast `callback* pp` from its base type to its actual type
// that has the rest of the data, which the event despatcher code knows nothing of.
// The event despatch code should not include the headers of the event handling code,
// as this would make possible breach of separation of responsibilities.
try{
(*((*pp).p))(pp, evtype, event );
}
catch(...){
// log error and release event handler.
// if an exception propagates from the event handling code into the event despatch code
// it is programming error, a violation of separation of responsibilities
// and the event despatch code cannot do anything with the error.
}
```
`pp` points into a blob that containst the data needed for handling the event when will happen,
and a pointer to the code that will handle it when it happens, ptrEvent is a ptr to a
struct containing an index that will tell us what kind of struct it is. It is a C union
of very different structs. Since it is dynamicaly allocated, we don't waste space, we create
the particular struct, cast it to the union, and then cast the union to the struct it actually is.
But, that C code will be quite happy if given a class whose first field is a pointer to a C calling
convention static member function that calls the next field, the
next field being a lambda whose unnameable type is known to the templated object when
it was defined, or if it is given a class whose first field is a pointer to a C calling convention
static function that does any esoteric C++, or Rust, or Lua thing.
2022-02-16 00:53:01 -05:00
# auto and decltype(variable)
In good c++, a tremendous amount of code behavior is specified by type
information, often rather complex type information, and the more one’ s
code description is in types, the better.
But specifying types everywhere violates the dry principle, hence,
wherever possible, use auto and decltype(variable) to avoid redundant
and repeated type information. Wherever you can use an auto or a
decltype for a type, use it.
In good event oriented code, events are not triggered procedurally, but
by type information or data structures, and they are not handled
2024-07-03 20:18:48 -04:00
procedurally, as by defining a lambda, but by defining a derived type
in the sense that you use the virtual method table as the despatch table
for handling different events.
2022-02-16 00:53:01 -05:00
# Variable length Data Structures
2024-06-28 03:52:41 -04:00
C++ just does not handle them well, except you embed an `std::vector` in them,
2022-02-16 00:53:01 -05:00
which can result in messy reallocations.
One way is to drop back into old style C, and tell C++ not to fuck
around.
2022-02-18 15:59:12 -05:00
struct Packet
{
unsigned int bytelength;
unsigned int data[];
private:
// Will cause compiler error if you misuse this struct
void Packet(const Packet&);
void operator=(const Packet&);
};
Packet* CreatePacket(unsigned int length)
{
Packet *output = (Packet* ) malloc((length+1)*sizeof(Packet));
output->bytelength = length;
return output;
}
2022-02-16 00:53:01 -05:00
# for_each
2022-02-18 15:59:12 -05:00
template< class InputIterator , class Function >
Function for_each(InputIterator first, InputIterator last, Function fn){
while (first!=last) {
fn (*first);
++first;
}
return move(fn);
}
2022-02-16 00:53:01 -05:00
# Range-based for loop
2022-02-18 15:59:12 -05:00
for(auto x: temporary_with_begin_and_end_members{ code;}
for(auto& x: temporary_with_begin_and_end_members{ code;}
for(auto& & x: temporary_with_begin_and_end_members{ code;}
for (T thing = foo(); auto& x : thing.items()) { code; }
2022-02-16 00:53:01 -05:00
The types of the begin_expr and the end_expr do not have to be the same,
and in fact the type of the end_expr does not have to be an iterator: it
just needs to be able to be compared for inequality with one. This makes
it possible to delimit a range by a predicate (e.g. “the iterator
points at a null character”).
If range_expression is an expression of a class type C that has both a
member named begin and a member named end (regardless of the type or
accessibility of such member), then begin_expr is \_\_range.begin() and
end_expr is \_\_range.end();
2022-02-18 15:59:12 -05:00
for (T thing = foo(); auto x : thing.items()) { code; }
2022-02-16 00:53:01 -05:00
Produces code equivalent to:
2022-02-18 15:59:12 -05:00
T thing = foo();
auto bar = thing.items();
auto enditer = bar.end;
for (auto iter = bar.begin(); iter != enditer; ++iter) {
x = *iter;
code;
}