Accessor is used to get and set value, it's a combination of Getter and Setter.
Usually you should use Accessor instead of Getter or Setter.
accessorpp/accessor.h
template <
typename Type,
typename PoliciesType = DefaultPolicies
>
class Accessor :
Type
: the underlying value type.
PoliciesType
: the policies.
accessorpp uses policy based design to configure and extend Accessor behavior. The last template parameter in Accessor is the policies class. Accessor has default policies class named DefaultPolicies
.
A policy is a type in the policies class. All policies must be public visible, so struct
is commonly used to define the policies class.
All policies are optional. If any policy is omitted, the default value is used. In fact DefaultPolicies
is just an empty struct.
The policy Storage
determines how the underlying data is stored. It can have two kinds of types,
accessorpp::InternalStorage
: store the data in the Accessor. This is the default type.
accessorpp::ExternalStorage
: the Accessor doesn't hold the data, how the data is accessed depending on the getter and setter in the Accessor.
InternalStorage and ExternalStorage defines different constructors and member functions in Accessor. You may treat them as different Accessor classes.
Example code,
struct MyPolicies
{
using Storage = accessorpp::InternalStorage;
};
accessorpp::Accessor<int, MyPolicies> accessor;
struct MyPolicies
{
using Storage = accessorpp::ExternalStorage;
};
int value = 0;
accessorpp::Accessor<int, MyPolicies> accessor(&value, &value);
OnChangingCallback specifies the event handler type that's called before the underlying value is changed. OnChangedCallback specifies the event handler type that's called before the underlying value is changed.
Ideally the CallbackList
in my eventpp library is perfect for such callback, otherwise std::function
can be used.
The callback can have three kinds of prototype, accessorpp will invoke the proper prototype automatically.
std::function<void ()>; // or eventpp::CallbackList<void ()>
std::function<void (const Type & newValue)>; // or eventpp::CallbackList<void (const Type & newValue)>
std::function<void (const Type & newValue, CallbackData data)>; // or eventpp::CallbackList<void (const Type & newValue), CallbackData data>
The callback type must be default constructible. To add a handler to the accessor, call void onChanging()
for OnChangingCallback and void onChanged()
for OnChangedCallback.
The default callback type is void
, that doesn't trigger any events.
Example code,
// Use std::function
struct MyPolicies
{
using OnChangedCallback = std::function<void (std::string)>;
};
accessorpp::Accessor<std::string, MyPolicies> accessor;
accessor.onChanged() = [](const std::string & newValue) {
std::cout << "New value is " << newValue << std::endl;
};
accessor = "Hello";
// Use eventpp::CallbackList
struct MyPolicies
{
using OnChangedCallback = eventpp::CallbackList<void (std::string)>;
};
accessorpp::Accessor<std::string, MyPolicies> accessor;
accessor.onChanged().append([](const std::string & newValue) {
std::cout << "New value is " << newValue << std::endl;
});
accessor = "Hello";
If CallbackData
is not void
, we can call Accessor::setWithCallbackData
to set the value with a callback data, and the callback data will be passed to OnChangingCallback and OnChangedCallback, if the callback accepts the data.
Calling Accessor::set
or assigning to an accessor will pass default constructed callback data.
The CallbackData
is useful to pass a "context" to the on change callback. For example, assume there is a text box on a GUI window. The text box listens to accessor's on change event to update the interface, while when the text box is changed such as the user types letters, the text box will also set to the accessor with the new text, which will also trigger the on change event. Without CallbackData, the text box needs update the interface two times when the user inputs, one is when the user typing, the other one is when the on change event is triggered by the text box. With CallbackData, when the text box sets the accessor, it can pass the CallbackData to indicate the setting is from itself, and in it's on change listener, it can check the CallbackData to avoid redundant updating.
The tutorial "tutorial_view_model_binding.cpp" in the tests source code demonstrate the mechanism clearly.
Accessor(const Type & newValue = Type());
Default constructor.
newValue
is the initial value.
template <typename G, typename S>
Accessor(G && getter, S && setter, const ValueType & newValue = ValueType());
The argument getter
and setter
are used to construct underlying accessorpp::Getter
and accessorpp::Setter
.
newValue
is the initial value.
getter
can be any type that's accepted by accessor::Getter
.
setter
can be any type that's accepted by accessor::Setter
.
getter
can be accessor::DefaultGetter
, means the default getter is used.
setter
can be accessor::DefaultSetter
, means the default setter is used.
setter
can be accessor::NoSetter
, means there no setter used, so the accessor is read only. Setting to an accessor which setter is NoSetter
will throw exception.
It's possible that the getter and setter gets and sets external value, then the internal storage of the value is wasted. In such case, accessorpp::ExternalStorage
should be used.
Note: the getter and setter should call accessor.directGet
and accessor.directSet
to access the internal value in the accessor.
Example code,
accessorpp::Accessor<int> accessor(
[&accessor]() {
std::cout << "Getting value" << std::endl;
return accessor.directGet();
},
[&accessor](const int value) {
std::cout << "Setting value " << value << std::endl;
accessor.directSet(value);
}
);
const int value = accessor;
accessor = value + 1;
template <typename G, typename IG, typename S, typename IS>
Accessor(
G && getter, IG && getterInstance,
S && setter, IS && setterInstance,
const ValueType & newValue = ValueType()
);
newValue
is the initial value.
getter
and getterInstance
are used to construct underlying accessorpp::Getter
.
setter
and setterInstance
are used to construct underlying accessorpp::Setter
.
Example code,
struct MyClass
{
void setValue(const int newValue) {
std::cout << "Setting value " << newValue << std::endl;
value = newValue;
}
int value;
};
MyClass instance { 5 };
accessorpp::Accessor<int> accessor(
&MyClass::value, &instance,
&MyClass::setValue, &instance
);
accessor = instance.value + 1;
Above constructor equals to,
accessorpp::Accessor<int> accessor(
accessorpp::Getter<int>(&MyClass::value, &instance),
accessorpp::Setter<int>(&MyClass::setValue, &instance)
);
Accessor(const Accessor & other);
Copy constructor.
Accessor(Accessor && other);
Move constructor.
const Type & directGet() const;
Type & directGet();
Get the internal value directly. This is used to implement customized getter.
void directSet(const Type & newValue);
Set the internal value directly. This is used to implement customized setter.
directSet
doesn't respect read-only accessor, so it can set the value in a read-only accessor.
directSet
doesn't trigger any onChanging/onChanged events.
Accessor() noexcept;
template <typename G, typename S>
Accessor(G && getter, S && setter);
template <typename G, typename IG, typename S, typename IS>
Accessor(
G && getter, IG && getterInstance,
S && setter, IS && setterInstance
);
Accessor(const Accessor & other);
Accessor(Accessor && other);
The difference between ExternalStorage and InternalStorage is, constructors for ExternalStorage don't have the argument for initial value(newValue
). ExternalStorage doesn't have functions directGet
and directSet
.
Below functions are available in both InternalStorage and ExternalStorage.
constexpr bool isReadOnly() const;
Return true if the accessor is read-only. Setting to a read-only accessor will throw std::logic_error
.
ValueType get(const void * instance = nullptr) const;
operator ValueType() const;
Get the value. The function is same as Getter::get
.
Accessor & set(const ValueType & newValue, void * instance = nullptr);
Set the value. The function is same as Setter::set
.
Accessor & setWithCallbackData(const ValueType & newValue, CD && callbackData, void * instance = nullptr);
Set the value with CallbackData.
Input/output stream operator are overload. Accessor uses the underlying value with the stream.
The instance
argument in above get/set/setWithCallbackData functions are used to pass the object instance explicitly, if the underlying getter/setter is constructed with member data or member function and doesn't bind to an instance. In such case, the instance
must be passed in explicitly, otherwise, the get/set/setWithCallbackData function will crash as if accessing an object of nullptr.
struct MyClass
{
void setValue(const int newValue) {
value = newValue;
}
int value;
};
MyClass instance;
accessorpp::Accessor<int> accessor(&MyClass::value, &MyClass::setValue);
accessor.set(15, &instance);
// Bang, crash. The instance is default nullptr
//accessor = 15;
// output 15
std::cout << accessor.get(&instance) << std::endl;
// Bang bang, crash. The instance is default nullptr
// std::cout << (int)accessor << std::endl;
accessor.set(16, &instance);
// output 16
std::cout << accessor.get(&instance) << std::endl;
std::ostream & operator << (std::ostream & stream, const Accessor & accessor);
std::istream & operator >> (std::istream & stream, Accessor & accessor);
// #1
template <typename T, typename G, typename S, typename Policies>
Accessor<T, Policies> createAccessor(G && getter, S && setter, Policies = Policies());
// #2
template <
typename T,
typename G, typename IG, typename S, typename IS,
typename Policies = DefaultPolicies
>
Accessor<T, Policies> createAccessor(
G && getter, IG && getterInstance,
S && setter, IS && setterInstance,
Policies = Policies()
);
// #3
template <typename G, typename S, typename Policies = DefaultPolicies>
auto createAccessor(G && getter, S && setter, Policies = Policies())
-> Accessor<typename private_::DetectValueType<G>::Type, Policies>;
// #4
template <
typename G, typename IG, typename S, typename IS,
typename Policies = DefaultPolicies
>
auto createAccessor(
G && getter, IG && getterInstance,
S && setter, IS && setterInstance,
Policies = Policies()
)
-> Accessor<typename private_::DetectValueType<G>::Type, Policies>;
Create accessor object using given arguments.
#1 and #2 create the accessor using the explicitly passed-in type T
.
#3 and #4 deducts the accessor type automatically.
// #1
template <typename T, typename G, typename Policies = DefaultPolicies>
Accessor<T, Policies> createReadOnlyAccessor(G && getter, Policies = Policies());
// #2
template <typename T, typename G, typename IG, typename Policies = DefaultPolicies>
Accessor<T, Policies> createReadOnlyAccessor(G && getter, IG && getterInstance, Policies = Policies());
// #3
template <typename G, typename Policies = DefaultPolicies>
auto createReadOnlyAccessor(G && getter, Policies = Policies())
-> Accessor<typename private_::DetectValueType<G>::Type, Policies>;
// #4
template <typename G, typename IG, typename Policies = DefaultPolicies>
auto createReadOnlyAccessor(G && getter, IG && getterInstance, Policies = Policies())
-> Accessor<typename private_::DetectValueType<G>::Type, Policies>;
Create read-only accessor object using given arguments.
#1 and #2 create the accessor using the explicitly passed-in type T
.
#3 and #4 deducts the accessor type automatically.