explode_time may only be
meaningful if another boolean (in Java) or bool (in C++)
property explode_mode is set to true. In C++ variables of
type bool, char, int, float, double and
pointers are initialised to no specific value, just whatever is the
contents of memory at the time the variable is constructed. Using
Submodes, errors for uninitialised properties can be found at
debug-time (when the symbol NDEBUG is not set), and the overhead
of the extra testing code can be optimised away into nothing in
release versions (when NDEBUG is set) for the same level of
performance as if Submodes were not used.
Here is how the above class can be split into two classes:class A { private int a1; // The following property is meaningful if a1 is 0 private int a2; public int a3; // The following property is meaningful if a3 is 0 public X a4; }
From this example, it should be self explanatory to generalise Submodes to arbitrary types and number of variables.class A_Inner { private int a1; private int a2; private int a3; private X a4; //////////////////////////////////////////////////////////// public int get_a1() { return a1; } public int set_a1(int value) { a1 = value; } //////////////////////////////////////////////////////////// public int get_a2() { if (a1 != 0) { throw new RuntimeException("a1 != 0"); } return a2; } public int set_a2(int value) { if (a1 != 0) { throw new RuntimeException("a1 != 0"); } a2 = value; } //////////////////////////////////////////////////////////// public int get_a3() { return a3; } public int set_a3(int value) { a3 = value; } //////////////////////////////////////////////////////////// public X get_a4() { if (a3 != 0) { throw new RuntimeException("a3 != 0"); } return a4; } public void set_a4(X value) { if (a3 != 0) { throw new RuntimeException("a3 != 0"); } a4 = value; } } class A { private A_Inner inner; public int get_a3() { return inner.get_a3(); } public void set_a3(int value) { return inner.set_a3(value); } public X get_a4() { return inner.get_a4(); } public void set_a4(X value) { inner.set_a4(value); } }
In the above the macrosclass A_Inner { SUBMODES_INNER_VAR(int,a1,,); SUBMODES_INNER_VAR(int,a2, { assert(get_a1() == 0); }, { assert(get_a1() == 0); }); SUBMODES_INNER_VAR(int,a3,,); SUBMODES_INNER_VAR_STAR(X,a4, { assert(get_a3() == 0); }, { assert(get_a3() == 0); }); }; class A { private: A_Inner inner; SUBMODES_OUTER_GET(int,a3); SUBMODES_OUTER_SET(int,a3); SUBMODES_OUTER_GET_STAR(X,a4); SUBMODES_OUTER_SET_STAR(X,a4); };
SUBMODES_INNER_VAR, SUBMODES_OUTER_GET
and SUBMODES_OUTER_SET should be used for types that can be passed
and returned by value. The macros SUBMODES_INNER_VAR_STAR,
SUBMODES_OUTER_GET_STAR and SUBMODES_OUTER_SET_STAR should by used
for types that are to be passed and returned by pointer.
Here is the definition of a class that is useful for debugging
uninitialised properties. This class differs from the bool type in
that safe_bool objects are initialised to false, compared with
no specific value for bool objects.
Here is the definition of the macros for theclass safe_bool { public: bool value; safe_bool() { value = false; } /// /// Needed for assignment sb = b /// safe_bool(bool b) { value = b; } operator bool() const { ASSERT(this != null); return value; } };
A_Inner class:
Here is the definition of the macros for the#ifdef NDEBUG \ #define SUBMODES_INNER_VAR(Type,variable,get_test,set_test) \ private: \ Type variable; \ public: \ Type get_ ## variable() const { \ return variable; \ } \ void set_ ## variable(Type value) \ { \ variable = value; \ } #else /* !NDEBUG */ #define SUBMODES_INNER_VAR(Type,variable,get_test,set_test) \ private: \ Type variable; \ safe_bool is_set_ ## variable; \ public: \ Type get_ ## variable() const { \ get_test; \ assert(is_set_ ## variable); \ return variable; \ } \ void set_ ## variable(Type value) \ { \ set_test; \ is_set_ ## variable = true; \ variable = value; \ } #endif /* NDEBUG */ #ifdef NDEBUG #define SUBMODES_INNER_VAR_STAR(Type,variable,get_test,set_test) \ private: \ Type* variable; \ public: \ Type* get_ ## variable() \ { \ return variable; \ } \ const Type* get_ ## variable ## _const() const \ { \ return variable; \ } \ void set_ ## variable(Type* new_value) \ { \ variable = new_value; \ } #else /* !NDEBUG */ #define SUBMODES_INNER_VAR_STAR(Type,variable,get_test,set_test) \ private: \ Type* variable; \ safe_bool is_set_ ## variable; \ public: \ Type* get_ ## variable() \ { \ get_test; \ assert(is_set_ ## variable); \ return variable; \ } \ const Type* get_ ## variable ## _const() const \ { \ get_test; \ assert(is_set_ ## variable); \ return variable; \ } \ void set_ ## variable(Type* new_value) \ { \ set_test; \ is_set_ ## variable = true; \ variable = new_value; \ } #endif /* NDEBUG */
A class:
I have inspected assembler code generated by Gnu C++ from source code that uses Submodes and have found that if the setting/getting tests are dropped when#define SUBMODES_OUTER_GET(Type,variable) \ public: \ Type get_ ## variable() const \ { \ ASSERT(this != null); \ return inner.get_ ## variable(); \ } #define SUBMODES_OUTER_SET(Type,variable) \ public: \ void set_ ## variable(Type new_value) \ { \ ASSERT(this != null); \ inner.set_ ## variable(new_value); \ } #define SUBMODES_OUTER_GET_STAR(Type,variable) \ public: \ Type* get_ ## variable() \ { \ ASSERT(this != null); \ return inner.get_ ## variable(); \ } \ const Type* get_ ## variable ## _const() const \ { \ return inner.get_ ## variable ## _const(); \ } #define SUBMODES_OUTER_SET_STAR(Type,variable) \ public: \ void set_ ## variable(Type* new_value) \ { \ ASSERT(this != null); \ inner.set_ ## variable(new_value); \ }
NDEBUG is set as is the case for the above macro
definitions, then the resulting code is just as efficient as if
Submodes were not used.
| Back to Research Projects |