El estado compartible estado sobre el que está preguntando no tiene nada que ver con mutlithreading. En su lugar, es un detalle de implementación de las clases de datos de copia en escritura (incluso los de un solo subproceso) que distribuyen referencias al estado interno.
Considérese una clase String
que se implementa utilizando vaca (para fines de ilustración, esta clase no es utilizable en contextos roscados, porque los accesos a d->refcount
no están sincronizados, sino que también no asegura que el char
arrary interno termina en '\0'
, y como bien podría comer su abuela, que han sido advertidos):
struct StringRep {
StringRep()
: capacity(0), size(0), refcount(0), sharable(true), data(0) {}
~StringRep() { delete[] data; }
size_t capacity, size, refcount;
bool sharable; // later...
char * data;
};
class String {
StringRep * d;
public:
String() : d(new StringRep) { ++d->refcount; }
~String() { if (--d->refcount <= 0) delete d; }
explicit String(const char * s)
: d(new StringRep)
{
++d->refcount;
d->size = d->capacity = strlen(s);
d->data = new char[d->size];
memcpy(d->data, s, d->size);
}
String(const String &other)
: d(other.d)
{
++d->refcount;
}
void swap(String &other) { std::swap(d, other.d); }
String &operator=(const String &other) {
String(other).swap(*this); // copy-swap trick
return *this;
}
y una función de ejemplo de cada para mutar y métodos const:
void detach() {
if (d->refcount == 1)
return;
StringRep * newRep = new StringRep(*d);
++newRep->refcount;
newRep->data = new char[d->size];
memcpy(newRep->data, d->data, d->size);
--d->refcount;
d = newRep;
}
void resize(size_t newSize) {
if (newSize == d->size)
return;
detach(); // mutator methods need to detach
if (newSize < d->size) {
d->size = newSize;
} else if (newSize > d->size) {
char * newData = new char[newSize];
memcpy(newData, d->data, d->size);
delete[] d->data;
d->data = newData;
}
}
char operator[](size_t idx) const {
// no detach() here, we're in a const method
return d->data[idx];
}
};
Hasta ahora todo bien. Pero, ¿y si queremos proporcionar un mutable operator[]
?
char & operator[](size_t idx) {
detach(); // make sure we're not changing all the copies
// in case the returned reference is written to
return d->data[idx];
}
Esta implementación ingenua tiene un defecto. Considere el siguiente escenario:
String s1("Hello World!");
char & W = s1[7]; // hold reference to the W
assert(W == 'W');
const String s1(s2); // Shallow copy, but s1, s2 should now
// act independently
W = 'w'; // modify s1 _only_ (or so we think)
assert(W == 'w'); // ok
assert(s1[7] == 'w'); // ok
assert(s2[7] == 'W'); // boom! s2[7] == 'w' instead!
Para evitar esto, String
tiene que marcar en sí no compartible cuando se reparte una referencia a los datos internos, por lo que cualquier copia que se toma de siempre es profunda. Por lo tanto, tenemos que ajustar detach()
y char & operator[]
así:
void detach() {
if (d->refcount == 1 && /*new*/ d->sharable)
return;
// rest as above
}
char & operator[](size_t idx) {
detach();
d->shareable = false; // new
return d->data[idx];
}
Cuando para restablecer el estado shareable
de nuevo a true
de nuevo? Una técnica común es decir que las referencias al estado interno se invalidan al llamar a un método no const, por lo que es donde shareable
se reinicia a true
. Debido a que cada función no constante llama detach()
, podemos restablecer shareable
allí, por lo que finalmente se convierte en detach()
:
void detach() {
if (d->refcount == 1 && d->sharable) {
d->sharable = true; // new
return;
}
d->sharable = true; // new
StringRep * newRep = new StringRep(*d);
++newRep->refcount;
newRep->data = new char[d->size+1];
memcpy(newRep->data, d->data, d->size+1);
--d->refcount;
d = newRep;
}
A pesar de que una búsqueda entre los doc qt, yo no tropezaron sobre eso .. muchas gracias – drahnr