L'état partageable que vous demandez n'a rien à voir avec mutlithreading. Il s'agit plutôt d'un détail d'implémentation de classes de données de copie à l'écriture (même celles à un seul thread) qui distribuent des références à l'état interne.
Tenir compte une classe String
qui est mis en œuvre à l'aide CoW (à des fins d'illustration, cette classe ne sont pas utilisables dans des contextes filetés, car les accès à d->refcount
ne sont pas synchronisés, il ne garantit pas que la char
interne se termine par arrary '\0'
, et pourrait aussi bien manger votre grand-mère, vous avez été mis en garde):
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;
}
et une fonction d'échantillonnage chacun pour les méthodes et const muter:
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];
}
};
Jusqu'ici tout va bien. Mais que faire si nous voulons fournir 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];
}
Cette implémentation naïve a un défaut. Considérez le scénario suivant:
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!
Pour éviter cela, String
doit se marquer non partageable quand il remet une référence aux données internes, de sorte que toute copie qui est tirée de c'est toujours profond. Donc, nous devons ajuster detach()
et char & operator[]
comme ceci:
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];
}
Quand réinitialiser l'état shareable
retour à true
à nouveau? Une technique courante consiste à dire que les références à l'état interne sont invalidées lors de l'appel d'une méthode non-const, c'est ainsi que shareable
est réinitialisé à true
. Étant donné que toutes les fonctions non-const appelle detach()
, nous pouvons réinitialiser il shareable
, de sorte que detach()
devient enfin:
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;
}
Bien que je recherche à travers le qt doc, je bronchassent à ce sujet .. merci beaucoup – drahnr