2

So an Instanced API is one that behaves like an object. So for example:

foo* GetInstancedAPI();
void MemFuncSetter(foo* fooThis, const int arg);
int MemFuncGetter(const foo* fooThis) const;

This is as opposed to a non-instanced API which would depend upon look-ups:

int GetInstancedAPI();
void MemFuncSetter(const int index, const int arg);
int MemFuncGetter(const int index) const;

A little background about the situation, this is a C API, that's being used to wrap a C++ implementation. So internally to the implementation I am working with objects. So I've tried to think through the ramifications of each, the biggest issues I can think of are:

  • How would I handle callbacks?
  • Is there a way minimize the lookup cost?

Edit:
There have been a lot of requests for clarifications: foo* is really a void pointer in the C interface which will be reinterpret_cast into a pointer to the actual C++ object, thus it must be passed in.
The functions that take an int are with the intention of indexing into an vector of objects in the wrapped C++.

Jonathan Mee
  • 169
  • 5
  • 1
    In an effort to quell the "Too Broad" close votes, I've removed the last sentence of your post asking for feedback, guidance and best practices. In general, Q&A works best when questions focus on a specific problem rather than general guidance. – Robert Harvey Feb 01 '18 at 17:06
  • What do you expect `foo *` to do that `int` doesn't do? – nwp Feb 01 '18 at 17:24
  • Does the `foo *` pointer point to the C++ object, or to some intermediary? – 1201ProgramAlarm Feb 01 '18 at 17:33
  • 1
    In the second API, what is the significance of `index` there? Is that supposed to be treated like an opaque handle that is passed the same way to these functions, and that the "index" is referring to some kind of array of global lifetime? That's bad because it makes tooling unhelpful, and enables doing operations that make no sense (what is the result of adding two handles?). – milleniumbug Feb 01 '18 at 17:45
  • 2
    insofar as `foo*` is an externally opaque pointer I don't see an issue with this unless there are security concerns. If there are security concerns (e.g. someone starts using your pointer by casting it to a struct they can access) you may need to do a non-instanced API. – Mgetz Feb 01 '18 at 17:50
  • 2
    Note that `const foo*` is a constraint you can't express with a `int index`. Why reinvent pointers badly if you can simply use a pointer? – amon Feb 02 '18 at 12:02

3 Answers3

7

Is there a way minimize the lookup cost?

Yes- don't use a lookup. Instanced APIs are the way to go - non-instanced ones suffer from terrible problems like not having separate instances be concurrently accessed from multiple threads, invisible dependencies and other such issues.

DeadMG
  • 36,794
  • 8
  • 70
  • 139
2

Callbacks are handled the same way every callback should be handled in C, a function pointer and a void* userdate/context pointer. The function pointer takes the parameters it needs and the context pointer which it can then use to access the state it needs.

Lookup cost is just one extra memory lookup compared to using globals for your state and no different than using C++ member functions which do the same thing (this gets passed as a hidden parameter).

ratchet freak
  • 25,706
  • 2
  • 62
  • 97
2

I see no practical difference between the example you've given.

In the first case you've returned a pointer to an object. In the second, you've returned an index of that object in an array.

C defines array access and pointer access as being essentially equivalent. array[index] = *(array + index). So in one case, you've done an addition (and possibly scaling) before returning the pointer, while in the other you do them while accessing the item.

In theory, that can/should favor the pointer (every so slightly), since it does the addition/scaling only once instead of multiple times. In reality, it's not necessarily that simple. On a modern system, a pointer will often be 64 bits. Since there's essentially no chance of your really wanting to be able to return 1.8x1019 separate items, you can typically use a much smaller number for your index--a single char is often more than sufficient.

In this case, loading and passing a char can be enough faster to more than compensate for the adding and scaling so using the index actually turns out (slightly) faster.

As for access from multiple threads and such go...it depends. It's certainly true that the index carries an implicit dependency on the array into which it indexes. If you're going to pass the index across thread boundaries, you have to ensure that the array is accessible from all the relevant threads (but with most systems, that's the default and you'd have to specify it explicitly to get something thread-local). At least in the normal code, that array is read-only, so you don't have to lock it to get access.

You might or might not need to do some locking in the object itself, but it's basically irrelevant, because using a pointer vs. index to find the object isn't going to affect how (or if) you need to do locking inside the object.

Jerry Coffin
  • 44,385
  • 5
  • 89
  • 162
  • I wanna +1 but I have to wait an hour (outta votes). The one thing I'd add (going further with the implicit array dependency you mentioned) is that while a thread-safe allocator would require thread synchronization, it doesn't require the sync to access the object pointee from pointer. If the object lives in an array, we end up dealing with invalidation issues which requires that we do end up requiring locking even just to access it via index, since the array could concurrently be reallocated during that time. –  Feb 01 '18 at 22:59
  • [...] That said, I often find the benefits worthwhile enough even when locking is needed for thread safety because of the locality of reference and the much smaller size of the indices (rarely ever need more than 32-bits), especially on 64-bit. –  Feb 01 '18 at 22:59