Недокументированный метод Qt setSharable - PullRequest
6 голосов
/ 27 марта 2010

Я наткнулся на метод, который, кажется, присутствует во всех объектах данных, таких как QList, QQueue, QHash ...

Я даже исследовал до сих пор, я вижу его исходный код, который

inline void setSharable(bool sharable) {
    if (!sharable) detach(); d->sharable = sharable;
}

in qlist.h (строки 117).

Но как это влияет на QList, QQueue, QHash ...? И связано ли это каким-либо образом с многопоточностью (что звучит разумно)?

Спасибо за любой ответ, и, пожалуйста, отвечайте, только если вы получили реальные знания.

Ответы [ 2 ]

6 голосов
/ 27 марта 2010

Никто не мог бы сказать более ясно:

http://qt.nokia.com/doc/4.6/implicit-sharing.html

Обычной практикой является реализация контейнеров таким образом.

5 голосов
/ 20 июня 2012

Состояние sharable , о котором вы спрашиваете, не имеет ничего общего с многопоточностью. Вместо этого это детали реализации классов данных копирования при записи (даже однопоточных), которые раздают ссылки на внутреннее состояние.

Рассмотрим класс String, который реализован с использованием CoW (в целях иллюстрации этот класс нельзя использовать в многопоточных контекстах, поскольку доступ к d->refcount не синхронизирован, он также не гарантирует, что внутренний char Артериальная оканчивается на '\0', и с тем же успехом может съесть вашу бабушку; вас предупредили):

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 должен пометить себя как неразделимый, когда он передает ссылку на внутренние данные, чтобы любая взятая из него копия всегда была глубокой. Итак, нам нужно настроить 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? Обычный метод - сказать, что ссылки на внутреннее состояние становятся недействительными при вызове неконстантного метода, поэтому shareable сбрасывается обратно на true. Так как каждая неконстантная функция вызывает 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;
    }
...