Weak Method Argument Semantics

How to properly pass along a weak ref to the caller of a function?

If your compute function is doing work asynchronously and getting a callback then it can capture your parameter weakly in that work's closure:

static func compute(forMe: Boss) {
doSomethingAsync { [weak forMe] in
// forMe will be nil in here if it was deallocated before this is called
forMe?.callback()
}
}

Why does weak attribute in gcc work differently in static library than for object files?

The purpose of weak is (in my mind) so the user can overwrite a function with their own when it is needed (or for the language internal usage, e.g., for inline),

Yes and no. I would narrow that a bit and change the focus: the purpose of weak symbols is so that a library can provide default implementations of certain functions that others of the library's functions use, yet also allow programs using that library to substitute their own implementations.

Moreover, although that does not preclude use by static libraries, it is intended primarily for dynamic libraries, because you don't need weak symbols for that purpose when you are linking static libraries. Shared libraries, on the other hand, need to be built differently and have support from the dynamic linker to allow such substitutions.

but when a library creator overwrites a function inside of the same (or underlying) archive it is not overwritten, the linker simply chooses the first definition it sees.

And? The creator of a static library has control of its contents, including their order. If they mean to provide a particular strong definition of a given symbol, then it is at minimum wasteful to put any other external definitions of that symbol into the same library.

Yes, a static linker could nevertheless cover for library authors on that score, but this scenario is not the one that weak symbols were designed to support, so why should linker maintainers be expected to expend work to implement that, and commit to maintaining it indefinitely?

Which is not consistent with the "flat" (libraryless) structure.

That depends on how you perform the link in the "flat" case. Also, linking a collection of (only) object files into a complete program is still not the scenario that weak symbols were designed to support.

why did they make it that way?

You would have to ask the designers if you want an authoritative answer, but my take is that the authors and maintainers of the GCC toolchain decided that the static link behavior you are asking about was a reasonable compromise between the complexity of the tools and the ideal semantics of weak symbols. They were likely influenced in this area by the SUN tools, which may effectively have been where the decision actually was made.

The chosen details serve the intended purpose for weak symbols just fine, and I am fairly confident that one library containing both weak and strong definitions of the same symbol was not among the use cases that guided the the tools' design. That the behavior of linking object files directly differs from that of linking them from a static library could be considered inconsistent, but that's a question of your mental model of the process. In any case, I am inclined to think that the question asked at the time was "what is the user most likely to mean?", and by no means is it clear that the answer to that would be the same for linking multiple object files (presumably all provided by the program / library author) as for linking a static library (frequently provided by a third-party).

What are the advantages of declaring method arguments __autoreleasing?

tl;dr

Implicitly converting a __weak object to a __strong object in this case would alter the semantic of the program, something that a compiler should never do.



The scenario

Let's take an example

NSError *error;
BOOL success = [myObject performOperationWithError:&error];
if (!success) {
// Report the error
}

In such a case the error local variable is automatically inferred by ARC as __strong.

At the same time the error argument of

-(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;

is of type NSError * __autoreleasing *.

Please note that in any case ARC will infer parameters passed by reference (id *) as being of type id __autoreleasing *, so the above signature is equivalent to

-(BOOL)performOperationWithError:(NSError **)error;

under ARC.

Therefore we have a mismatch since we are passing a __strong annotated variable to a method expecting an __autoreleasing argument.

Under the hood

In our example then the compiler will address such mismatch by creating a local __autoreleasing tmp variable.

The code becomes

NSError * __strong error;
NSError * __autoreleasing tmp = error;
BOOL success = [myObject performOperationWithError:&tmp];
error = tmp;
if (!success) {
// Report the error.
}

An alternative

Let's now pretend that we can change the signature of performOperationWithError:.

If we want to avoid the "compiler trick" which uses the tmp variable, we can declare our signature as

-(BOOL)performOperationWithError:(NSError * __strong *)error;

We have a __strong variable and we are now passing it to a method expecting a __strong argument, so we just eliminated the mismatch.

Looks good, why not always declare __strong arguments?

One reason is that declaring the argument as __autoreleasing will make the method to accept even a __weak reference.

It does not make much sense in the current example, but there could be cases in which we'd like to pass a __weak variable by reference and declaring __autoreleasing (or leaving the ARC to infer it) will allow us to do so.

ARC will apply the same trick seen above, creating a __autoreleasing tmp variable.

Conclusion

The mechanism presented so far goes under the name of pass-by-writeback.

Such mechanism has been designed to work with __autoreleasing, __strong and __weak variables, so that the programmer can safely rely on the type inference made by the compiler and not care too much about annotating variables around.

Declaring a id __strong * argument may make sense in some cases, but in general it could lead to unexpected errors generated by the compiler.

My advice here is: "let the compiler do his magic and you'll be good"

Update

I don't see why compiler can not do the same trick for __strong argument.

Telling the compiler to handle in an __autoreleasing fashion the management of either a __strong or __weak variable it's ok since it basically means: "Please, compiler, do the right thing automatically".

That's why the trick seen above will work without issues.

On the other hand, if you declare a variable as __weak you presumably have a good reason for doing so and the last thing you want is to have it implicitly retained when you clearly specified otherwise. That would radically change the semantic of the piece of code you've written, therefore the compiler won't do that (thank God!).

In other words

__weak --> __autoreleasing good

__strong --> __autoreleasing good

__weak <--> __strong wrong!

Move semantics when sending object as function's parameter

An rvalue reference is (surprise:) a reference indeed.

You can move from it, but std::move does not move.

So, if you don't move from it, you'll actually operate on the rvalue object (through the rvalue reference).


The usual pattern would be

void foo(X&& x)
{
X mine(std::move(x));

// x will not be affected anymore
}

However, when you do

void foo(X&& x)
{
x.stuff();
x.setBooDitty(42);
}

effectively, X&& is just acting as a traditional reference

Why should I use Weak/SoftReference with ReferenceQueue argument?Why cannot I poll original reference and check if null?

With little modification your code would produce anticipated result. See code snippet below.

In your code, line

pRef.get() == null

would assign pRef.get() to temporary slot in frame of method run(). Event after condition is calculated, slot is not clear automatically.

Garbage collector treat all slots/local variable in active frames on stack as GC roots. Unintentionally you are creating strong reference to your object so it wont be cleared.

I modified version, I have moved pRef.get() to nested method. Once execution returns from method its frame is disposed, so to reference to object remains to prevent GC to collect it.

Of cause, if JVM would recompile run() method and inline isRefernceCollected(pRef) call, it may break again.

In summary, reference queues give you deterministic and efficient way to handle reference. Pooling can work but it is fragile and depends of code compilation by javac and JVM JIT.

Modified code snippet.

public class TestPhantomRefQueue {

public static void main(String[] args)
throws InterruptedException {

Object obj = new Object();
final ReferenceQueue queue = new ReferenceQueue();

final WeakReference pRef =
new WeakReference(obj, queue);

obj = null;

new Thread(new Runnable() {
public void run() {
try {
System.out.println("Awaiting for GC");

while (true) {
if (isRefernceCollected(pRef)) {
Thread.sleep(100);
break;
}
}
System.out.println("Referenced GC'd");
System.out.println(pRef.get());

} catch (Exception e) {
e.printStackTrace();
}
}

protected boolean isRefernceCollected(final WeakReference pRef) {
return pRef.get() == null;
}
}).start();

// Wait for 2nd thread to start
Thread.sleep(2000);

System.out.println("Invoking GC");
System.gc();
}
}

Output

Awaiting for GC
Invoking GC
Referenced GC'd
null


Related Topics



Leave a reply



Submit