[ ... ]
I just can't see how deep-copying solves any synchronization problems.
The only way to copy the string from A to B safely is to lock access to
it for the duration of the copying (regardless of how the copying is
done). I can't see any other way.
It's pretty simple really: regardless of the function I call to get
the data to the new thread, the calling of the function happens in
the original thread. There's a sequence point after evaluating the
arguments to the function and executing any code inside that
function, so by the time we've entered the function, the copy is
complete, and the code inside the function no longer has any access
to the original data at all.
A typical implementation of that function is going to take the
parameters from the stack, copy them into some structure (probably
dynamically allocated) and pass the address of that structure to the
new thread. This is necessary because the data needs to survive for
some length of time that's indeterminate to the calling thread, and
may easily extend until well after the function returns. Even in an
atypical implementation, however, the code in that function can't do
anything with the original object, because all it gets is a copy, not
any access to the original object.
If you were absolutely determined to shoot yourself in the foot, you
could have that function send the address of the second copy of the
string (the one that was actually passed to the new thread) _back_ to
other code in the original thread. At that point, you _would_ have
shared access to that copy of the string, and you'd have to
synchronize access accordingly. Likewise, you could write a copy ctor
that disguised a pass by reference as a pass by value (e.g. each copy
would contain a vector of address of objects from which it was
copied, so the copy gave access not only to the data, but also to the
original object from which the data derived.
Once the new thread is running, there's no way for the parent thread
to directly invoke any code in the new thread. Communication is only
via putting data somewhere, and the new thread picking that data up
from there. As when starting a thread, however, calling such a
function happens entirely in the parent thread, so that function will
only ever have access to a copy of anything that was passed by value.