Converting Examples into Libraries

Much of the example code that I publish is released to embedded-resources as standalone .c or .cpp files. I structure examples in this way as a matter of convenience:

  • It's easier for consumers to parse and understand a self-contained example
  • Each example usually needs its own main function to demonstrate usage

However, in their monolithic forms, these files are not directly usable in your projects. Let's take a look at how I translate from an example to a library.

A Note on Organization

I try keep all of my standalone library code in a top-level lib/ folder. My goal for these libraries is that they should be usable no matter the underlying system.

When I setup my include paths, I usually just include the lib/ directory only, not individual library directories. When I include files I need to include them in this form: dispatch/dispatch.h. I find this is a cleaner implementation than managing hundreds of include paths.

Bonus points: other developers who are not familiar with your project have a much better idea of where to look for more info on the library!

Here's an organizational snapshot from one of my projects:

 

Write here...

 

Example 1: dispatch.cpp

Let's take a look at converting the dispatch.cpp example into an actual library. dispatch.cpp is a great initial candidate, as it requires taking the standalone file and splitting it into separate .h and .cpp files.

Please take a moment to look over the dispatch.cpp implementation so you have a mental representation of the example code.

Library Header

When I'm adding new code, the first thing I like to tackle is the header file. While probably a matter of habit, taking time to focus on consumer-facing interfaces and definitions puts me in a better architectural state-of-mind.

Since you're splitting up an example file into a header and source file, understanding what needs to be moved into the header is very important.

Step 1: Identify What Needs to Move

Here are some rules of thumb that I use for moving items into a header:

  • Function prototypes that need to be made public
  • Class declarations
  • Definitions or types that are used in function/class prototypes
  • Definitions or types that are generally useful for consumers
  • Take note of headers that are included in the source. You likely need to move some of these from the source to the header.

Looking at dispatch.cpp, we only need to pull in the class dispatch_queue declaration and some of the headers required due to the class members (std::string, std::mutex, :std::vector, std::queue, std::condition_variable).

Step 2: Construct the Header

Now that we know what needs to be pulled in, let's work on building our library header.

There are two general header features that I take care of up front: header guards and extern "C".

Header Guards

I recommend adding header guards to all of your headers. If you're not familiar, this is what header guards look like:

#ifndef DISPATCH_H_
#define DISPATCH_H_

...

#endif //DISPATCH_H_

This compiler directive wraps the entire header contents. Each header should define a different symbol. This symbol is usually related to the filename in some way to reduce the odds of a collision. You must make sure that your headers do not have matching guard definitions.

Header guards prevent you from including the same header multiple times. Guards are also useful for preventing circular include dependencies.

If you are including the header in a context for the first time, the #ifndef check will be true and the header will be included (also #define-ing the header-guard symbol).

If the header is included a second time, the #ifndef statement will no longer be true and the compiler will not define the header contents a second time.

extern "C"

If you have a library that can be shared between C and C++, you will need to make the functions visible in the C namespace.

Accomplish this by adding the following around any functions that need to be available in C:

#ifdef __cplusplus
extern "C"
{
#endif

...

#ifdef __cplusplus
} // extern "C"
#endif

We will see an example using extern "C" later, as our dispatch library does not currently need it.

Completed dispatch.h

Here's what our new dispatch.h file looks like:

#ifndef DISPATCH_H_
#define DISPATCH_H_

#include <thread>
#include <functional>
#include <vector>
#include <queue>
#include <mutex>
#include <string>
#include <condition_variable>

class dispatch_queue {
    typedef std::function<void(void)> fp_t;

public:
    dispatch_queue(std::string name, size_t thread_cnt = 1);
    ~dispatch_queue();

    // dispatch and copy
    void dispatch(const fp_t& op);
    // dispatch and move
    void dispatch(fp_t&& op);

    // Deleted operations
    dispatch_queue(const dispatch_queue& rhs) = delete;
    dispatch_queue& operator=(const dispatch_queue& rhs) = delete;
    dispatch_queue(dispatch_queue&& rhs) = delete;
    dispatch_queue& operator=(dispatch_queue&& rhs) = delete;

private:
    std::string name_;
    std::mutex lock_;
    std::vector<std::thread> threads_;
    std::queue<fp_t> q_;
    std::condition_variable cv_;
    bool quit_ = false;

    void dispatch_thread_handler(void);
};

#endif //DISPATCH_H_

Pretty simple!

Library Source

After we've removed the items that belong in the header, now it's time to clean up the library source.

  • Remove main() function
  • Remove any convenience functions that were used just for examples
  • Remove any demo-specific includes or examples (e.g. <cstdio>)
  • Keep function definitions

Starting with the header also simplifies the .cpp case. We are just pruning away the unnecessary items from our implementation!

Completed dispatch.cpp

Now that we've moved definitions into the header, taken away main, and stripped out any unnecessary #includes and functions, we're ready to rock and roll. Let's take a look at my completed dispatch.cpp file. Notice that I only kept the definitions for class dispatch_queue.

#include "dispatch.h"

#ifdef DISPATCH_DEBUG
#include <cstdio>
#define DEBUG_PRINT printf
#else
#define DEBUG_PRINT(...)
#endif

dispatch_queue::dispatch_queue(std::string name, size_t thread_cnt) :
    name_(name), threads_(thread_cnt)
{
    DEBUG_PRINT("Creating dispatch queue: %s\n", name.c_str());
    DEBUG_PRINT("Dispatch threads: %zu\n", thread_cnt);

    for(size_t i = 0; i < threads_.size(); i++)
    {
        threads_[i] = std::thread(
            std::bind(&dispatch_queue::dispatch_thread_handler, this));
    }
}

dispatch_queue::~dispatch_queue()
{
    // Signal to dispatch threads that it's time to wrap up
    quit_ = true;
    DEBUG_PRINT("Destructor: Destroying dispatch threads...\n");

    // Wait for threads to finish before we exit
    for(size_t i = 0; i < threads_.size(); i++)
    {
        if(threads_[i].joinable())
        {
            DEBUG_PRINT("Destructor: Joining thread %zu until completion\n", i);
            threads_[i].join();
        }
    }
}

void dispatch_queue::dispatch(const fp_t& op)
{
    std::unique_lock<std::mutex> lock(lock_);
    q_.push(op);

    // Manual unlocking is done before notifying, to avoid waking up
    // the waiting thread only to block again (see notify_one for details)
    lock.unlock();
    cv_.notify_all();
}

void dispatch_queue::dispatch(fp_t&& op)
{
    std::unique_lock<std::mutex> lock(lock_);
    q_.push(std::move(op));

    // Manual unlocking is done before notifying, to avoid waking up
    // the waiting thread only to block again (see notify_one for details)
    lock.unlock();
    cv_.notify_all();
}

void dispatch_queue::dispatch_thread_handler(void)
{
    std::unique_lock<std::mutex> lock(lock_);

    do {
        //Wait until we have data or a quit signal
        cv_.wait(lock, [this]{
            return (q_.size() || quit_);
        });

        //after wait, we own the lock
        if(q_.size() && !quit_)
        {
            auto op = std::move(q_.front());
            q_.pop();

            //unlock now that we're done messing with the queue
            lock.unlock();

            op();

            lock.lock();
        }
    } while (!quit_);
}
 

Example 2: aligned smart pointers

The aligned smart pointer example is interesting: you can convert the example code into a standalone library header.

For C++ standalone headers, I like to mark them as .hpp so I know that including that library is not an option in my C code.

As before, please take a look at smart_ptr_aligned.cpp so you have the example fresh in your mind.

Prerequisites

I assume with this example that you have treated aligned_malloc in the same way as the dispatch example.

Pruning the Example

Following the rules we laid out in the dispatch.cpp example above, we can see that most of the smart_ptr_aligned.cpp file is cruft. We can eliminate:

  • main()
  • aligned_free_wrapper
    • Instead, we should just use the normal aligned_free function we defined in our aligned_malloc library
  • #include <string>
  • #include <cstdint>
  • #include <cstdio>

Library Header

Once we've removed the cruft, we're definitely left with this tidy little header:

#ifndef ALIGNED_PTR_H_
#define ALIGNED_PTR_H_

#include <memory>
#include <aligned_malloc/aligned_malloc.h>

template<class T> using unique_ptr_aligned = std::unique_ptr<T, decltype(&aligned_free)>;

template<class T>
unique_ptr_aligned<T> aligned_uptr(size_t align, size_t size)
{
    return unique_ptr_aligned<T>(static_cast<T*>(aligned_malloc(align, size)), &aligned_free);
}


template<class T>
std::shared_ptr<T> aligned_sptr(size_t align, size_t size)
{
    return std::shared_ptr<T>(static_cast<T*>(aligned_malloc(align, size)), &aligned_free);
}

#endif
 
I hope I've illuminated ways you can take my example code and use it in your system. Happy hacking!