logo
vision scalability social networking revelation

C++ Automatic Memory Management

1 Memory Safety

Modern, mostly memory safe C++, is enforced by:

$ 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.

static_assert(__cplusplus >= 201703, "C version of out of date");

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.

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)

 auto sp = std::make_shared<int>(42);
std::weak_ptr<T> wp{sp};

2 Array sizing and allocation

    /*  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

// (usually 4) struct alignas(float) struct_float { // your definition here };

// every object of type sse_t will be aligned to 256-byte boundary struct alignas(256) sse_t { float sse_data[4]; };

// the array “cacheline” will be aligned to 128-byte boundary alignas(128) char cacheline[128]; ```

3 Construction, assignment, and destruction

six things: (default constructor, copy constructor, move constructor, copy assignment, move assignment and 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

A(const A& a)

Copy assignment

    A& operator=(const A other)

Move constructors

class_name ( class_name && other)
  A(A&& o)
  D(D&&) = default;

Move assignment operator

V& operator=(V&& other)

Move constructors

class_name ( class_name && )

3.1 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.

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.

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:

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);
}

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.

3.2 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.

struct foo {
    foo() noexcept {}
    foo( const foo & ) noexcept { }
    foo( foo && ) noexcept { }
    ~foo() {}
};

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.

    t2 = t1;  /* calls assignment operator, same as "t2.operator=(t1);" */
    Test t3 = t1;  /* calls copy constructor, same as "Test t3(t1);" */

3.3 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.

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.
};

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.

4 Template specialization

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

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.

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.*/

5 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

6 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:.

class IErrorLog
{
public:
    virtual bool openLog(const char *filename) = 0;
    virtual bool closeLog() = 0;

    virtual bool writeError(const char *errorMessage) = 0;

    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;
};

Override specifier

struct A
{
    virtual void foo();
    void bar();
};

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
};

Similarly Final specifier

To obtain aligned storagefor use with placement new

    void* p = aligned_alloc(sizeof(NotMyClass));
    MyClass* pmc = new (p) MyClass; //Placement new.
    // ...
    pmc->~MyClass();    //Explicit call to destructor.
    aligned_free(p);.

7 GSL: Guideline Support Library

The Guideline Support Library (GSL) contains functions and types that are suggested for use by the C++ Core Guidelines maintained by the Standard C++ Foundation. This repo contains Microsoft’s implementation of GSL.

   git clone https://github.com/Microsoft/GSL.git
    cd gsl
    git tag
    git checkout tags/v2.0.0

Which implementation mostly works on gcc/Linux, but is canonical on Visual Studio.

For usage of spans (the replacement for bare naked non owning pointers subject to pointer arithmetic)

For usage of string spans (String spans These are pointers to char arrays. There does not seem to be a UTF‑8 string_span.

GSL is a preview of C++20, as boost contained a preview of C++11.

It is disturbingly lacking in official documentation, perhaps because still subject to change.

Unofficial documentation

It provides an optional fix for C’s memory management problems, while still retaining backward compatibility to the existing pile of rusty razor blades and broken glass.

8 The Curiously Recurring Template Pattern

CRTP, 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.

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" ){ ...}
}

9 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

Class T{
    T(std::initializer_list<const unsigned char> in){
        for (auto i{in.begin); i<in.end(); i++){
        do stuff with i
    }
}

but that does not make it of aggregate type. Aggregate type has no constructors except default and deleted constructors

10 functional programming

A lambda is a nameless value of a nameless class that is a functor, which is to say, has operator() defined.

But, of course you can get the class with decltype and assign that nameless value to an auto variable, or stash it on the heap with new, or in preallocated memory with placement new

But if you are doing all that, might as well explicitly define a named functor class.

To construct a lambda in the heap:

auto p = new  auto([a,b,c](){})

Objects inside the lambda are constructed in the heap.

similarly placement new, and unique_ptr.

To template a function that takes a lambda argument:

template <typename F>
void myFunction(F&& lambda){
    //some things

You can put a lambda in a class using decltype,and pass it around for continuations, though you would probably need to template the class:

template<class T>class foo {
public:
    T func;
    foo(T in) :func{ in } {}
    auto test(int x) { return func(x); }
};
    ....
    auto bar = [](int x)->int {return x + 1; };
    foo<(bar)>foobar(bar);

But we had to introduce a name, bar, so that decltype would have something to work with, which lambdas are intended to avoid. If we are going to have to introduce a compile time name, easier to do it as an old fashioned function, method, or functor, as a method of a class that is very possibly pod.

If we are sticking a lambda around to be called later, might copy it by value into a templated class, or might put it on the heap.

auto bar = []() {return 5;};

You can give it to a std::function:

auto func_bar = std::function<int()>(bar);

In this case, it will get a copy of the value of bar. If bar had captured anything by value, there would be two copies of those values on the stack; one in bar, and one in func_bar.

When the current scope ends, func_bar will be destroyed, followed by bar, as per the rules of cleaning up stack variables.

You could just as easily allocate one on the heap:

auto bar_ptr = std::make_unique(bar);

std::function <int(int)> increm{[](int arg{return arg+1;}}

presumably does this behind the scenes

On reflection we could probably use this method to produce a templated function that stored a lambda somewhere in a templated class derived from a virtual base class for execution when the event triggered by the method fired, and returned a hashcode to the templated object for the event to use when the event fired. The event gets the event handler from the hashcode, and the virtual base class in the event handler fires the lambda in the derived class, and the lambda works as a continuation, operating in the context wherein it was defined, making event oriented programming almost as intuitive as procedural programming.

But then we have a problem, because we would like to store event handlers in the database, and restore them when program restarts, which requires pod event handlers, or event handlers constructible from POD data, which a lambda is not.

We could always have some event handlers which are inherently not POD and are never sent to a database, while other event handlers are, but this violates the dry design principle. To do full on functional programming, use std::function and std::bind, which can encapsulate lambdas and functors, but are slow because of dynamic allocation

C++ does not play well with functional programming. Most of the time you can do what you want with lambdas and functors, using a pod class that defines operator(...)

11 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 procedurally, as by defining a lambda, but by defining a derived type.

12 Variable length Data Structures

C++ just does not handle them well, except you embed a vector in them, which can result in messy reallocations.

One way is to drop back into old style C, and tell C++ not to fuck around.

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;
}

Another solution is to work around C++’s inability to handle variable sized objects by fixing your hash function to handle disconnected data.

13 for_each

template<class InputIterator, class Function>
    Function for_each(InputIterator first, InputIterator last, Function fn){
    while (first!=last) {
        fn (*first);
        ++first;
    }
    return move(fn);
}

14 Range-based for loop

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; }

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();

for (T thing = foo(); auto x : thing.items()) { code; }

Produces code equivalent to:

T thing = foo();
auto bar = thing.items();
auto enditer = bar.end;
for (auto iter = bar.begin(); iter != enditer; ++iter) {
    x = *iter;
    code;
}

Creative Commons License reaction.la gpg key 154588427F2709CD9D7146B01C99BB982002C39F
This work is licensed under the Creative Commons Attribution 4.0 International License.