Qt недокументированный метод setSharable
я наткнулся на метод, который, похоже, присутствует во всех объектах данных, таких как QList
, QQueue
, QHash
...
Я даже исследовал до сих пор, я вижу исходный код этого, который
inline void setSharable(bool sharable) {
if (!sharable) detach(); d->sharable = sharable;
}
на qlist.h (строки 117).
но как это влияет на QList
, QQueue
, QHash
... ? И связано ли это каким-либо образом с потоковой обработкой (что звучит разумно)?
Спасибо за любой ответ, и пожалуйста, отвечайте только если у тебя есть настоящие знания.
2 ответов
на sharable государство вы спрашиваете не имеет ничего общего с mutlithreading. Вместо этого это деталь реализации классов данных copy-on-write (даже однопоточных), которые выдают ссылки на внутреннее состояние.
рассмотрим класс String
это реализовано с помощью CoW (для целей иллюстрации этот класс не используется в потоковых контекстах, потому что обращается к d->refcount
не синхронизированы, это также не гарантирует, что внутренние char
arrary заканчивается ''
, и мог бы также съесть свою бабушку; вас предупредили):
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;
}
и пример функции каждый для мутирующих и 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];
}
};
пока все хорошо. Но что, если мы хотим обеспечить изменяемость 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];
}
эта наивная реализация имеет недостаток. Рассмотрим следующий сценарий:
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!
чтобы не допустить этого, String
должен отметить себя не-sharable, когда он раздает ссылку на внутренние данные, так что любая копия, взятая из него, всегда глубока. Итак, нам нужно настроить detach()
и char & operator[]
такой:
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];
}
когда сброс shareable
государство обратно в true
снова? Общий метод состоит в том, чтобы сказать, что ссылки на внутреннее состояние недействительны при вызове метода non-const, так что вот где shareable
сбрасывается обратно в true
. Поскольку каждая функция non-const вызывает detach()
, мы можем сбросить shareable
есть, так что 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;
}
никто не мог сказать яснее:
http://qt.nokia.com/doc/4.6/implicit-sharing.html
Это обычная практика, чтобы реализовать контейнеры таким образом.