2

note: this question is related to weak_ptr usage, but is not about wrapping weak_ptrs.

I am currently evaluating Swig and I have found an "inconvenience" in the usage of the wrappers by the client languages, that I have not found described online and for which I have no satisfactory solution.

In C++ if you have a complex graph of objects that are managed using shared_ptrs, you must take special care when the graph can have a cycle (i.e. if it is not a DAG) or else you will get memory leaks. By that I mean that if you must (cannot avoid) to have a cycle, it must contain at least one weak_ptr. This means that you will have to handle the cases where you cannot lock the weak_ptr, because the related shared_ptr has died. This management is something that one can expect C++ programmers to be used to deal with. Now let's look at what can happen for a user of the wrappers:

So let's take the following example:

  • object a, held by a shared_ptr, has a shared_ptr to b
  • object b, held by a shared_ptr, has a weak_ptr to a

The following could happen to a user of the wrapper:

A a
B b = a->GetB()
// here let's suppose that a gets out of scope, so it can be garbage collected
b->GetA() // fails if a has been garbage collected

The failure could be cleanly managed by propagating a C++ exception to the client code (throw if cannot lock the weak_ptr to create a shared_ptr). However this is not idiomatic to Python/C#/Java users: they do not except to have to manually keep some objects alive to access others.

I have a draft of a an alternative solution which involves creating a C++ "Co-Owner" of objects a and b that would be locked by the SWIG wrappers when any of a or b are accessed via the wrappers, thus keeping both a and b alive when any of them is accessed via the wrappers. The downsides are that this starts to look like I am implementing a proto-garbage-collection in C++, also this modifies the C++ implementation & API of objects a & b and finally the objects a & b will have to be notified by the wrappers that they are used via the wrappers (not something I think I can do without patching SWIG to add function calls in constructor and destructor of shadow objects ??).

Have I missed anything ? Is there another solution to this problem ?

Gabriel Devillers
  • 3,155
  • 2
  • 30
  • 53
  • 1
    *Is there another solution to this problem ?* If by problem, you mean how can I make my code behave as if it had garbage collection, then rolling your own garbage collection is probably the way to go. – super Nov 05 '21 at 10:20

1 Answers1

3

Publicly available weak pointers

If weak ownership is actually part of the interface, it is possible to bind extra types manually that exhibit the behavior of weak pointers. In this example they create a type FooWeakPtr providing the relevant interface. But as you can see, this is not taking advantage of the language-specific classes in every language.

Internal weak pointers

If the weak pointers are not part of the interface, then SWIG-generated bindings should not care about them, and treat the bound objects as shared pointers (at least in Python and Java). Therefore, for as long as your objects are available from the other language, you must make sure they all stay alive.

That means it is up to you to design your hierarchy of objects in a way that clients can never face a case where an internal weak pointer is invalid, and that dropping every reference actually leads to the destruction of the hierarchy.

The solution actually resides in the details of your object hierarchy, and SWIG cannot do much about it.

Gabriel Devillers
  • 3,155
  • 2
  • 30
  • 53
Victor Paléologue
  • 2,025
  • 1
  • 17
  • 27