Objects¶
Introduction¶
An FSTR::Object
is a class template with array-like behaviour, though it is not used directly.
Instead, use one of the four classes in the library:
Each type has its own set of macros for easy data construction, and creation of the appropriate Object class which may then be used directly.
Macros follow the same pattern:
DEFINE_FSTR_*
- Creates a static data structure with an associated Object reference.
The _LOCAL variant makes the reference
static constexpr
. DECLARE_FSTR_*
- Use this in a header to declare an Object reference so it can be used across translation units.
Created symbols are C++ and adopt any enclosing namespaced.
Reading Object content¶
To read parts of an Object, use the FSTR::Object::read()
method.
If the data isn’t used very often, use the FSTR::Object::readFlash()
method instead as it avoids
disrupting the cache. The FSTR::Stream
class (alias FlashMemoryStream
) does this by default.
Object Internals¶
This section provides some examples of how structures are created, but in normal use you should use the provided macros as they simplify the task and include structure validity checks.
FSTR::ObjectBase
is a non-template
POD
base class, and looks like this (methods omitted):
class ObjectBase {
uint32_t flashLength_;
// uint8_t data[];
};
Attention
flashLength_
must not be accessed directly; use the length()
method instead.
Data structures are created like this:
constexpr const struct {
ObjectBase object;
char data[8];
} flashHelloData PROGMEM = {
{5},
"hello"
};
The object
field may then be cast to a reference of the required type, like this:
auto& str = flashHelloData.object.as<FSTR::String>();
If you want to access it as an array, do this:
auto& arr = str.as<FSTR::Array<char>>();
References are an efficient and convenient way to access an Object, and should not consume any memory themselves as the compiler/linker resolve them to the actual object.
However, in practice the Espressif compiler stores a full pointer to most things to support relative addressing, and if the references aren’t declared PROGMEM they’ll consume RAM.
Copy behaviour¶
Whilst references are the preferred way to access flash Objects, they can also be created dynamically:
FSTR::String emptyString;
FSTR::String stringCopy(FS("Inline string"));
Such instances are stored in RAM but only consume 4 bytes as they simply keep a pointer to the real flash Object.
Note
Don’t try to copy ObjectBase!
Here’s a somewhat contrived example to demonstrate:
DEFINE_FSTR_DATA_LOCAL(flashHelloData, "Hello");
auto myCopy = flashHelloData.object;
Serial.print("myCopy.length() = ");
Serial.println(myCopy.length());
In debug builds, this will throw an assertion. In release builds, you’ll get a zero-length object.
Aggregate initialization¶
We use aggregate initialization to set up the structures so the data is fixed at link time without any constructor or initialiser functions.
This means classes cannot have:
- user-provided constructors
- brace-or-equal-initializers for non-static data members
- private or protected non-static data members
- virtual functions
- base classes (until C++17)
This is why FSTR::ObjectBase
is used to define data structures.
Classes created using the FSTR::Object
template ensures the necessary constructors
are available to do this:
auto myCopy = flashHelloData.object.as<FSTR::String>();
Serial.print("myCopy.length() = ");
Serial.println(myCopy.length());
The macros create an appropriate Object& reference for you.
Structure checks¶
The construction macros include a sanity check to ensure the initialization is truly just Plain Old Data, without any hidden initialisers.
You may encounter one of the following errors during compilation:
- The value of ‘X’ is not usable in a constant expression
- FSTR structure not POD
This generally means one or more of the arguments in the initialisation data is not constexpr
.
Most compilers are quite relaxed about this but GCC 4.8.5
is particularly thick.
In testing, this happens with references for global Objects, which of course cannot be constexpr.
To fix it, the offending Object either needs to be redefined LOCAL, or if the Object data is in
scope (i.e. defined in the same source file) then you can get a direct pointer to it using
the FSTR_PTR()
macro.
Macros¶
-
DECLARE_FSTR_OBJECT
(name, ObjectType)¶ Declare a global Object reference.
- Parameters
name
:ObjectType
:
-
DEFINE_FSTR_REF
(name, ObjectType, object)¶ Define a reference to an object.
- Parameters
name
: Name for referenceObjectType
: Fully qualified typename of object required, e.g. FSTR::String, FlashString, FSTR::Vector<int>, etc.object
: Object instance to cast
-
DEFINE_FSTR_REF_NAMED
(name, ObjectType)¶
-
FSTR_DATA_NAME
(name)¶ Provide internal name for generated flash string structures.
-
FSTR_PTR
(objref)¶ Given an Object& reference, return a pointer to the actual object.
However, some older compilers such as GCC 4.8.5 requires such references to be declared constexpr. For example, this fails with
FSTR structure not POD
:DEFINE_FSTR(globalStringRef, "This creates a global reference"); DEFINE_VECTOR(myVector, FSTR::String, &globalStringRef); ^^^
- Parameters
objref
: When an Object pointer is required, such when defining entries for a Vector or Map, it is usually sufficient to use &objref.
Global references cannot be declared constexpr, so changing DEFINE_FSTR to DEFINE_FSTR_LOCAL will fix the problem.
Another solution is to get a direct pointer to the actual data structure:
DEFINE_VECTOR(myVector, FSTR::String, FSTR_PTR(globalStringRef));
We can only do this of course if the data structure is in scope.
-
FSTR_CHECK_STRUCT
(name)¶ Check structure is POD-compliant and correctly aligned.
-
IMPORT_FSTR_OBJECT
(name, ObjectType, file)¶ Import an object from an external file with reference.
- See
- See also
IMPORT_FSTR_DATA
- Note
- Can only be used at file scope
- Parameters
name
: Name for the objectObjectType
: Object type for referencefile
: Absolute path to the file containing the content
-
IMPORT_FSTR_OBJECT_LOCAL
(name, ObjectType, file)¶ Like IMPORT_FSTR_OBJECT except reference is declared static constexpr.
Class Template¶
-
template <class ObjectType, typename ElementType>
classObject
: public FSTR::ObjectBase¶ Base class template for all types.
- See
- https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
- Template Parameters
ObjectType
: The object type actually being instantiatedElementType
:
Public Types
-
template<>
usingIterator
= ObjectIterator<ObjectType, ElementType>¶
Public Functions
-
Object
()¶ Creates a null object.
-
Object
(const Object &obj)¶ Copy constructor.
- Note
- Objects are usually passed around by reference or as a pointer, but for ease of use we need a working copy constructor.
-
Iterator
begin
() const
-
Iterator
end
() const
-
size_t
length
() const Get the length of the content in elements.
-
template <typename ValueType>
intindexOf
(const ValueType &value) const
-
ElementType
valueAt
(unsigned index) const
-
ElementType
operator[]
(unsigned index) const Array operator[].
-
size_t
elementSize
() const
-
const ElementType *
data
() const
-
size_t
read
(size_t index, ElementType *buffer, size_t count) const Read content into RAM.
- Parameters
index
: First element to readbuffer
: Where to store datacount
: How many elements to read
- Return Value
size_t
: Number of elements actually read
-
size_t
readFlash
(size_t index, ElementType *buffer, size_t count) const Read content into RAM,using
flashmem_read()
- Parameters
index
: First element to readbuffer
: Where to store datacount
: How many elements to read
- Return Value
size_t
: Number of elements actually read
-
size_t
size
() const Get the object data size in bytes.
- Note
- Always an integer multiple of 4 bytes
-
template <class ObjectType>
constexpr const ObjectType &as
() const Cast to a different object type.
- Note
- example:
fstr.as<Array<int>>();
-
size_t
read
(size_t offset, void *buffer, size_t count) const Read contents of a String into RAM.
- Parameters
offset
: Zero-based offset from start of flash data to start readingbuffer
: Where to store datacount
: How many bytes to read
- Return Value
size_t
: Number of bytes actually read
-
size_t
readFlash
(size_t offset, void *buffer, size_t count) const Read contents of a String into RAM, using flashread()
PROGMEM data is accessed via the CPU data cache, so to avoid degrading performance you can use this method to read data directly from flash memory. This is appropriate for infrequently accessed data, especially if it is large. For example, if storing content using
IMPORT_FSTR
instead of SPIFFS then it is generally better to avoid contaminating the cache.- See
- See also
FlashMemoryStream
class. - Parameters
offset
: Zero-based offset from start of flash data to start readingbuffer
: Where to store datacount
: How many bytes to read
- Return Value
size_t
: Number of bytes actually read
-
bool
isCopy
() const
Public Members
-
uint32_t
flashLength_
Public Static Functions
-
static const ObjectType &
empty
() Return an empty object which evaluates to null.