1

The X: I have a collection of unrelated container-like objects (vector, maps, trees,...) handling objects of different unrelated types. The lifetime of the objects in these containers is shared between some subsets of them. I have an object in charge of their synchronization. The simplest implementation of a synchronization class that I can think of would be to have a vector of BaseContainerLike pointers, where BaseContainerLike would be a class implementing a common interface for all of the container-like objects that I want to manage. But not all of them are container like. They could be used as container like, but making them inherit from a common base class feels very weird and I'm scared that it will couple my design very strongly.

So I've created a ContainerLikeInterface class like this:

struct ContainerLikeInterface {
template<T>
ContainerLikeInterface(T& t) 
 : create([](int i){ return t->create(i); }),    // this is just an example
   delete([](int i){ return t->delete(i); }) {}

std::function<void(int)> create;
std::function<void(int)> delete;
};

template<class T>
ContainerLikeInterface make_containerLikeInterface(T& t) {
  return ContainerLikeInterface(t);
}

This allows me to trivially create a vector of interfaces in a non-intrusive way (I can just partially specialize the constructor for different types). My code using this approach is slightly faster than when using inheritance, but it requires slightly more memory and longer compile times (but I don't prioritize compile times). I don't know however if this approach will scale well with my project. And I've read some articles about value-semantics in which people prefer to transfer the object ownership to the interface, so I have the following questions:

  • What are the pros/cons of this approach?
  • Is this going to give me some problems in the long term?
  • Should I use inheritance instead?
  • Should I implement this in a different way?
  • Should I use a library instead? (boost::TypeErasure, adobe::poly, or pyrtsa/poly)
gnzlbg
  • 7,135
  • 5
  • 53
  • 106
  • 1
    Whether or not this is a good thing depends entirely on the problem you're trying to solve, which you've omitted. That is, why are you in this situation in the first place? – GManNickG Mar 06 '13 at 19:28
  • @GManNickG I've added more information about my specific problem explaining how I ended up in this situation. – gnzlbg Mar 06 '13 at 19:58

3 Answers3

1

Your interface slightly resembles the interface system that is made in Rust object system. Classic VMT-based interfaces have a pointer to an object which holds a pointer to a VMT. Yours have 2 pointers: one to an object and another to a method table. It [almost] always looks to have more power than virtual functions with disadvantages you already mentioned (memory usage etc.) As for the speed, std::function uses standard allocator to keep a pointer to t. If you call ContainerLikeInterface constructor a lot, it may cause some perfomance decrease because it needs at least one allocation per std::function in your interface and you could write your own for this.

Dmitry Galchinsky
  • 2,181
  • 2
  • 14
  • 15
  • That depends on the std::function implementation, see http://probablydance.com/2013/01/13/a-faster-implementation-of-stdfunction/ – gnzlbg Mar 08 '13 at 17:18
  • thank you for the link about rust, it looks really interesting! – gnzlbg Mar 09 '13 at 01:39
  • Still you are right, calling ContainerLikeInterface a lot will probably have an overhead due to calls to malloc. I could use a custom pool allocator, but in this case I think its not worth it because ContainerLikeInterface is for containers, and I don't have that many of those. Thanks for the thought tho, I will consider it in the future when writing containers of Interfaces like this. – gnzlbg Mar 11 '13 at 19:40
  • Thanks for this way to interface in C++, I'll definetly use it. Besides a time needed to allocate, heap (or even pool) allocation slows the program because it happens to be less cache friendly than in-place allocation. When everything is local you obviously need less places to store in CPU cache. I never tested this though, only read. – Dmitry Galchinsky Mar 11 '13 at 20:27
1

Your basic idea (do not require inheritance) is good. I would recommend using Adobe.Poly instead. When you use 1 std::function per single operation you have N sort-of virtual table (pointers), plus potentially N heap allocations (depending if SBO (Small Buffer Optimization) can be applied or not).

You are also very likely to get into object life-time management problems. In your implementation you assume that the real object lives longer than the "interface". Sooner or later you will get it wrong. This is why I would encourage a value-semantic approach. Adobe.Poly gives you that.

With Adobe.Poly you only get one vtable (pointer). It also implements SBO: potentially not a single allocation.

I would not necessarily go with Boost.TypeErasure. It requires learning another "language" for specifying interfaces, which exploits lots of meta programming, and as of today it does not implement SBO.

Adobe.Poly is not well documented. See this post for examples of how you use it. Also, see this paper on how it is implemented.

Andrzej
  • 5,027
  • 27
  • 36
  • Hey I learned about Poly from your posts earlier this year, thanks for writing those. I however disagree with you about value semantics by default. I agree that it is safer, but I think that which one is best depends strongly on the application. From a performance point of view, you want reference semantics for polymorphic access, and value semantics and array containers for monomorphic access (and thight loops). OTOH if you are dealing with, e.g., a GUI and have widgets and do not care about the exact types of the widgets, value semantics simplifies ownership and is thus better. – gnzlbg Dec 04 '14 at 10:19
  • Hmm. I certainly do agree with the statement that whether you need reference or value semantics depends on the application. I am just not sure that performance is this type of 'application'. If you have measured the cost of using value-semantic approach versus reference-semantics, then you are better to judge. My motivation for using reference semantics is when I need to access the very same object from two different locations (e.g., because I need to observe the changes in its state). – Andrzej Dec 05 '14 at 12:21
0

You are essentially creating a proxy object with ContainerLikeInterface interface with a pointer or reference to some T.

There is a way to avoid creating the proxy, while still using the standard containers which can not derive ContainerLikeInterface. It is probably called Mix-in design patter and with perfect forwarding in C++11 it would look like:

#include <vector>

struct IContainerLikeInterface
{
    virtual void create(int) = 0;
    virtual void erase(int) = 0;
};

template<class T>
struct ContainerLikeInterface : T, IContainerLikeInterface
{
    template<class... Args>
    ContainerLikeInterface(Args&& ...args) 
        : T(std::forward<Args>(args)...)
    {}

    // implement IContainerLikeInterface
    void create(int) override;
    void erase(int) override;
};

int main() {
    ContainerLikeInterface<std::vector<int> > v(10);
    v.size();
}

However, it is intrusive because all declarations of containers in question, like std::vector<int>, have to be changed into ContainerLikeInterface<std::vector<int> >. The usage stays the same though, because ContainerLikeInterface<T> is-a T.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • Thanks! But when using a mixing (implemented with a CRTP) each interface will have a different type. That is, you can't have a vector of interfaces. – gnzlbg Mar 06 '13 at 19:46
  • @gnzlbg Oh, sorry I did not get you right the first time. The answer updated. – Maxim Egorushkin Mar 06 '13 at 20:27
  • Thanks for updating the answer. My containers are implemented already as mixins from a base container class (using also the CRTP). This is what I defined in my post as the simplest alternative. The cool thing about this is that you can use static polymorphism "where you can" and still be able to do dynamic polymorphism. However my containers have a lot of very small inline functions. From the moment I defined a virtual function gcc stopped inlining everything, so that is one of the reasons of switching to what I'm doing right now... Either that or switch to full run-time polymorphism... – gnzlbg Mar 07 '13 at 08:02
  • See also this question: http://stackoverflow.com/questions/14922890/whats-the-cost-of-calling-a-virtual-function-in-a-non-polymorphic-way – gnzlbg Mar 07 '13 at 08:10