开发者

Qt undocumented method setSharable

I stumbled about a method which seems to be present in all data objects like QList, QQueue, QHash...

I even investigated so far I can see the source code of it, which is

inline void setSharable(bool sharable) {
    if (!sharable) detach(); d->s开发者_如何转开发harable = sharable;
}

in qlist.h (lines 117).

But what effect does it have on the QList, QQueue, QHash... ? And is it in any way related to threading (which sounds reasonable)?

Thanks for any answer, and please only answer if you got actual knowledge.


No one could say more clear:

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

It is common practice to realize containers this way.


The sharable state you're asking about has nothing to do with mutlithreading. It is instead an implementation detail of copy-on-write data classes (even single-threaded ones) that hand out references to internal state.

Consider a class String that is implemented using CoW (for illustration purposes, this class isn't usable in threaded contexts, because accesses to d->refcount aren't synchronised, it also doesn't ensure that the internal char arrary ends in '\0', and might as well eat your grandmother; you have been warned):

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;
    }

And a sample function each for mutating and const methods:

    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];
    }

};

So far so good. But what if we want to provide a 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];
    }

This naïve implementation has a flaw. Consider the following scenario:

    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!

To prevent this, String has to mark itself non-sharable when it hands out a reference to internal data, so that any copy that is taken from it is always deep. So, we need to adjust detach() and char & operator[] like this:

    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];
    }

When to reset the shareable state back to true again? A common technique is to say that references to internal state are invalidated when calling a non-const method, so that's where shareable is reset back to true. Since every non-const function calls detach(), we can reset shareable there, so that detach() finally becomes:

    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;
    }
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜