Quantcast
Channel: Анал со зрелой
Viewing all articles
Browse latest Browse all 47

std::vector and Minimizing Includes

$
0
0

I like std::vector quite a bit except for a couple side-effects. Here are the side-effects I do not enjoy.

  1. Debug builds are slow due to copious debug checks.
  2. Constructors and destructors are *always* called upon insertion/removal of elements. This can even happen in some implementations during vector growth or assignment operations.
  3. Since the vector type is templatized, knowledge of the type must be present in headers.

For me the killer is #3. Requiring the full type declaration in a header often times brings in many unnecessary lines of code just to fulfill this template requirement. This brings down compile times.

Often times a header only needs a forwarded pointer type, and not a full type definition. This does not seem to be as much of a problem with the advent of C++17, but personally I do not enjoy writing code to explicitly always require a contemporary compiler.

Instead, I find it a lot nicer in my own personal code to use a couple macros. Writing out a growable array shouldn’t require C++ template knowledge, or compile-time nonsense, or constructors or destructors or any other baggage. Sometimes all that’s needed is a growable array, and that’s it.

struct container_t
{
	// Has all kinds of data members up here...

	// In-place vector of just a few fields.
	int object_capacity;
	int object_count;
	object_t* objects;
};

void push_object(container_t* container, object_t object)
{
	BUFFER_CHECK_THEN_GROW(container_t, object_count, object_capacity, objects, object_t, 256);
	container->objects[container->object_count++] = *object;
}

object_t pop_object(container_t* container)
{
	assert(container->object_count);
	return container->objects[--container->object_count];
}

I have been using these three macros ubiquitously for a few years now, and decided they are preferable over std::vector due to aforementioned reasons, and preferable over a templatized solution in favor of disliking C++ templates. There are many reasons to dislike templates, but this starts getting into philosophy.

These macros are probably not that great professionally at work, as people will be confused due to mismatched expectations, but they’ve worked great for my own code. It’s hardly any code, and will be easier on compile times in practice when compared to std::vector.

#define BUFFER_GROW(ctx, count, capacity, data, type, new_cap) \
	do { \
		int new_capacity = new_cap; \
		void* new_data = malloc(sizeof(type) * new_capacity); \
		memcpy(new_data, ctx->data, sizeof(type) * ctx->count); \
		free(ctx->data); \
		ctx->data = (type*)new_data; \
		ctx->capacity = new_capacity; \
	} while (0)

#define BUFFER_CHECK_THEN_GROW(ctx, count, capacity, data, type, initial) \
	do { \
		if (ctx->count == ctx->capacity) \
		{ \
			BUFFER_GROW(ctx, count, capacity, data, type, ctx->capacity ? ctx->capacity * 2 : initial); \
		} \
	} while (0)

#define BUFFER_SWAP_WITH_LAST(ctx, index, count, data) \
	do { \
		ctx->data[index] = ctx->data[--ctx->count]; \
	} while (0)

At the end of the day it is tens of lines of code, great performance, and can’t really get much simpler.

It is probably wise to use realloc instead of malloc/free, but eh, this works fine as well.

TwitterRedditFacebookShare


Viewing all articles
Browse latest Browse all 47

Trending Articles