e72e773ad0
SkTCast is functionally equivalent to reinterpret_cast. The comment about SkTCast helping to avoid strict alising issues is not true. Dereferencing a pointer cast to a pointer of an unrelated type is always undefined, even if smuggled through a union like in SkTCast. To really avoid aliasing issues, you need to make a union[1] of the two value types, or better, memcpy between values. I've had to fix MatrixText.cpp where switching to reinterpret_cast actually let Clang notice and warn that we're exploiting undefined behavior, and GrSwizzle.h and SkCamera.cpp caught by GCC. I've switched SkTLList over to use SkAlignedSTStorage, which seems to help convince some GCC versions that fObj is used in a sound way. [1] The union punning trick is non-standard in C++, but GCC and MSVC both explicitly support it. I believe Clang does not officially explicitly support it, but probably does quietly for GCC compatibility. Change-Id: I71822e82c962f9aaac8be24d3c0f39f4f8b05026 Reviewed-on: https://skia-review.googlesource.com/134947 Commit-Queue: Mike Klein <mtklein@chromium.org> Commit-Queue: Brian Salomon <bsalomon@google.com> Auto-Submit: Mike Klein <mtklein@chromium.org> Reviewed-by: Brian Salomon <bsalomon@google.com>
210 lines
5.7 KiB
C++
210 lines
5.7 KiB
C++
/*
|
|
* Copyright 2011 Google Inc.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#ifndef SkTLazy_DEFINED
|
|
#define SkTLazy_DEFINED
|
|
|
|
#include "../private/SkTemplates.h"
|
|
#include "SkTypes.h"
|
|
#include <new>
|
|
#include <utility>
|
|
|
|
/**
|
|
* Efficient way to defer allocating/initializing a class until it is needed
|
|
* (if ever).
|
|
*/
|
|
template <typename T> class SkTLazy {
|
|
public:
|
|
SkTLazy() : fPtr(nullptr) {}
|
|
|
|
explicit SkTLazy(const T* src)
|
|
: fPtr(src ? new (fStorage.get()) T(*src) : nullptr) {}
|
|
|
|
SkTLazy(const SkTLazy& that) : fPtr(nullptr) { *this = that; }
|
|
SkTLazy(SkTLazy&& that) : fPtr(nullptr) { *this = std::move(that); }
|
|
|
|
~SkTLazy() {
|
|
if (this->isValid()) {
|
|
fPtr->~T();
|
|
}
|
|
}
|
|
|
|
SkTLazy& operator=(const SkTLazy& that) {
|
|
if (that.isValid()) {
|
|
this->set(*that.get());
|
|
} else {
|
|
this->reset();
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
SkTLazy& operator=(SkTLazy&& that) {
|
|
if (that.isValid()) {
|
|
this->set(std::move(*that.get()));
|
|
} else {
|
|
this->reset();
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Return a pointer to an instance of the class initialized with 'args'.
|
|
* If a previous instance had been initialized (either from init() or
|
|
* set()) it will first be destroyed, so that a freshly initialized
|
|
* instance is always returned.
|
|
*/
|
|
template <typename... Args> T* init(Args&&... args) {
|
|
if (this->isValid()) {
|
|
fPtr->~T();
|
|
}
|
|
fPtr = new (reinterpret_cast<T*>(fStorage.get())) T(std::forward<Args>(args)...);
|
|
return fPtr;
|
|
}
|
|
|
|
/**
|
|
* Copy src into this, and return a pointer to a copy of it. Note this
|
|
* will always return the same pointer, so if it is called on a lazy that
|
|
* has already been initialized, then this will copy over the previous
|
|
* contents.
|
|
*/
|
|
T* set(const T& src) {
|
|
if (this->isValid()) {
|
|
*fPtr = src;
|
|
} else {
|
|
fPtr = new (reinterpret_cast<T*>(fStorage.get())) T(src);
|
|
}
|
|
return fPtr;
|
|
}
|
|
|
|
T* set(T&& src) {
|
|
if (this->isValid()) {
|
|
*fPtr = std::move(src);
|
|
} else {
|
|
fPtr = new (reinterpret_cast<T*>(fStorage.get())) T(std::move(src));
|
|
}
|
|
return fPtr;
|
|
}
|
|
|
|
/**
|
|
* Destroy the lazy object (if it was created via init() or set())
|
|
*/
|
|
void reset() {
|
|
if (this->isValid()) {
|
|
fPtr->~T();
|
|
fPtr = nullptr;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns true if a valid object has been initialized in the SkTLazy,
|
|
* false otherwise.
|
|
*/
|
|
bool isValid() const { return SkToBool(fPtr); }
|
|
|
|
/**
|
|
* Returns the object. This version should only be called when the caller
|
|
* knows that the object has been initialized.
|
|
*/
|
|
T* get() const { SkASSERT(this->isValid()); return fPtr; }
|
|
|
|
/**
|
|
* Like above but doesn't assert if object isn't initialized (in which case
|
|
* nullptr is returned).
|
|
*/
|
|
T* getMaybeNull() const { return fPtr; }
|
|
|
|
private:
|
|
SkAlignedSTStorage<1, T> fStorage;
|
|
T* fPtr; // nullptr or fStorage
|
|
};
|
|
|
|
/**
|
|
* A helper built on top of SkTLazy to do copy-on-first-write. The object is initialized
|
|
* with a const pointer but provides a non-const pointer accessor. The first time the
|
|
* accessor is called (if ever) the object is cloned.
|
|
*
|
|
* In the following example at most one copy of constThing is made:
|
|
*
|
|
* SkTCopyOnFirstWrite<Thing> thing(&constThing);
|
|
* ...
|
|
* function_that_takes_a_const_thing_ptr(thing); // constThing is passed
|
|
* ...
|
|
* if (need_to_modify_thing()) {
|
|
* thing.writable()->modifyMe(); // makes a copy of constThing
|
|
* }
|
|
* ...
|
|
* x = thing->readSomething();
|
|
* ...
|
|
* if (need_to_modify_thing_now()) {
|
|
* thing.writable()->changeMe(); // makes a copy of constThing if we didn't call modifyMe()
|
|
* }
|
|
*
|
|
* consume_a_thing(thing); // could be constThing or a modified copy.
|
|
*/
|
|
template <typename T>
|
|
class SkTCopyOnFirstWrite {
|
|
public:
|
|
explicit SkTCopyOnFirstWrite(const T& initial) : fObj(&initial) {}
|
|
|
|
explicit SkTCopyOnFirstWrite(const T* initial) : fObj(initial) {}
|
|
|
|
// Constructor for delayed initialization.
|
|
SkTCopyOnFirstWrite() : fObj(nullptr) {}
|
|
|
|
SkTCopyOnFirstWrite(const SkTCopyOnFirstWrite& that) { *this = that; }
|
|
SkTCopyOnFirstWrite( SkTCopyOnFirstWrite&& that) { *this = std::move(that); }
|
|
|
|
SkTCopyOnFirstWrite& operator=(const SkTCopyOnFirstWrite& that) {
|
|
fLazy = that.fLazy;
|
|
fObj = fLazy.isValid() ? fLazy.get() : that.fObj;
|
|
return *this;
|
|
}
|
|
|
|
SkTCopyOnFirstWrite& operator=(SkTCopyOnFirstWrite&& that) {
|
|
fLazy = std::move(that.fLazy);
|
|
fObj = fLazy.isValid() ? fLazy.get() : that.fObj;
|
|
return *this;
|
|
}
|
|
|
|
// Should only be called once, and only if the default constructor was used.
|
|
void init(const T& initial) {
|
|
SkASSERT(nullptr == fObj);
|
|
SkASSERT(!fLazy.isValid());
|
|
fObj = &initial;
|
|
}
|
|
|
|
/**
|
|
* Returns a writable T*. The first time this is called the initial object is cloned.
|
|
*/
|
|
T* writable() {
|
|
SkASSERT(fObj);
|
|
if (!fLazy.isValid()) {
|
|
fLazy.set(*fObj);
|
|
fObj = fLazy.get();
|
|
}
|
|
return const_cast<T*>(fObj);
|
|
}
|
|
|
|
const T* get() const { return fObj; }
|
|
|
|
/**
|
|
* Operators for treating this as though it were a const pointer.
|
|
*/
|
|
|
|
const T *operator->() const { return fObj; }
|
|
|
|
operator const T*() const { return fObj; }
|
|
|
|
const T& operator *() const { return *fObj; }
|
|
|
|
private:
|
|
const T* fObj;
|
|
SkTLazy<T> fLazy;
|
|
};
|
|
|
|
#endif
|