Creating a Dependency Injection Library in C++
Part 2 Parameters Counter
8/28/2018
The previous article has solved the difficulty of retrieving the types of services that a type being injected needs. This article discusses how to get the number of services (parameters) that a type being injected needs.
Constructors in C++ can have both required and optional parameters and multiple overload constructors. Because of this, it is impossible to get the number of parameters that a constructor needs accurately.
To inaccurately get the parameters a constructor needs, developers may ask the compiler if a type can be constructed with a specific list of arguments. If the constructor accepts a list of arguments, then the length of the list is the number of parameters the constructor needs. However, this only works if a constructor has optional parameters or multiple overload constructors.
std::is_constructible
can be used to check if a specific list of arguments can construct a type.
std::is_constructible<T, Args...>
struct A
{
A(int, int) {}
}
// yes
static_assert(std::is_constructible<A, PlaceHolder, PlaceHolder>::value, "constructible");
std::integer_sequence / std::index_sequence
Then, with the type mentioned in the first article, PlaceHolder
and std::integer_sequence
and recursive template functions, it is possible to use std::is_constructible
to get the number of parameters a constructor needs.
Creating a Dependency Injection Library in C++
Part 3 Service Binding
8/28/2018
The previous article discusses how to determine the number of parameters a function accepts. This article discusses the implementation of dependency injection.
Bindings
may be used to describe the types of service interfaces and the types of implementations of services. Developers may use a template to create a Binding
class.
Implementation of a Binding
A simplest implementation of a Binding
may look like the following:
template <typename T>
struct Binding
{
using typename Type = T;
std::function<T()> provider;
};
Binding<int> myBinding{[]{ return 10; }};
In the above example, the type alias Type
is the type of a service interface; the provider
is a provider that creates an implementation of the service type; the myBinding
is a provider that returns an integer with a value of 10.
Bindings
should be stored in the PlaceHolder
type mentioned in the previous articles, which makes the PlaceHolder
type an actual injector. Inside the injector, a container may store all the Bindings
a program has. This article uses std::tuple
as the container.
auto bindings = std::make_tuple(Binding<int>([]{ return 10; }), Binding<float>([] { return 2.1f; }));
Asynchronous Socket with C++ Coroutine TS
Part 3 Win32 IOCP API
8/26/2018
Almost every IO-related API in Windows allows an Overlapped
parameter, allowing developers to call the APIs asynchronously.
Before discussing the IOCP APIs, it is necessary to introduce APIs for creating and interacting with thread pools. Those functions help developers to use the callback mechanism of IOCP APIs.
An ordinary practice of using IOCP for asynchronous IO is as follows:
- Start an IO thread pool, and bind it to a socket;
- Start an asynchronous IO operation;
- When the operation is done, windows will call the callback function on the IO thread pool.
The CreateThreadpoolIo
API is used to start an IO thread pool.
CreateThreadpoolIo
PTP_IO CreateThreadpoolIo(
HANDLE fl,
PTP_WIN32_IO_CALLBACK pfnio,
PVOID pv,
PTP_CALLBACK_ENVIRON pcbe
);
Creating a Dependency Injection Library in C++
Part 1 Parameter Placeholders
8/26/2018
In other programming languages, developers use the reflection mechanism to implement dependency injection libraries. However, the static reflection features have not been added to C++ yet. However, it does not mean there are no ways to achieve it in C++. This article is to discuss a new method being able to achieve dependency injection in C++.
The most challenging part of doing dependency injection is getting the type of services needed by the class being injected. There are no ways to do this in C++. However, developers may use template meta-programming and implicitly type conversion to achieve it.
Imagine type PlaceHolder
can be converted to any other type, and type A
accepts four different types of parameters to construct itself, then four objects of A
can be used to create an instance of type B
.
E.g.
struct A
{
A(int, bool, double, int)
{
}
};
// The normal way to create an object of `A`
A a(1, true, 1.2, 3);
struct PlaceHolder
{
// The type `PlaceHolder` now supprts to be converted to any types
Asynchronous Socket with C++ Coroutine TS
Part 2 Implement your Coroutine.
8/19/2018
Promise and Future
Before we dive into the coroutine itself, we should get familiar with the Promise
and the Future
. Compared with callbacks, the Promise
provides a consistent way to return a result or an error in a non-blocking function, and the Future
provides a consistent way to retrieve them.
future<int> do_something_async(int a)
{
std::shared_promise<int> p;
// A non-blocking function using callback functions
// This function is from a library, and the first parameter of the callback function is the result
// and the second one is the errorCode
nonblocking_fetch_data_from_db([p, a](int result, int errorCode)
{
// This function is from another library, and the first parameter of the callback function is an
// errorCode but with a different type, and the second parameter is the file data
nonblocking_read_file([p](error_code errorCode, const std::string& fileData)
{
p.set_result(std::stoi(fileData));
});
});
return p.get_future();
Asynchronous Socket with C++ Coroutine TS
Part 1 Coroutines in C++
8/17/2018
The coroutine
Finally, the coroutine feature is set to add to C++. But wait, what does the coroutine do, and why should we use it? The coroutine feature solves the Callback hell
issue, which most front-end developers probably have experienced. It basically makes the asynchronous code looks like synchronised code but without blocking the executing thread.
The following code is from a socket server, which uses callback functions to handle new connections with a very basic handshake validation. It basically shows how the Callback hell
destroys developers' minds.
void s1_callback(error_code ec);
void c1_callback(error_code ec);
void s2_callback(error_code ec);
void c2_callback(error_code ec);
void handle_connection()
{
auto s1_buffer = std::shared_ptr<BYTE[]>(new BYTE[HANDSHAKE_S1_SIZE]);
auto self = shared_from_this();
read_async(socket, s1_buffer.get(), s1_buffer.size(), [=](){
self->s1_callback();
});
}
void s1_callback(error_code ec)