How to gracefully declare a subset of multiple variables

lets say that this is a requirement: As a user of the class I would like to get information about the subject, and when the class has enough information, I would like the class to return me a list of collected data. Sufficient information is defined as - when all information is collected from a subset of all possible information. This subset is not fixed and is provided to the class.

For example, this is a list of all possible information:

{ string name; int age; char sex; string location; } 

and I want to give my users the opportunity to tell me what I'm listening from some data source (from which my class analyzes data) until I get age and gender.

The problem is that I don’t know how to pass this without listing. Basically, my enum solution listens to the data source until I determine the use with std :: includes of 2 sets of enumerations (collected, necessary) that I collected all the data.

Can this be done without listing?

+7
c ++
source share
5 answers

Whenever I want to separate the implementation of logic from where it is needed - in this case, knowing “how much data is enough” - I think of the callback function.

Presumably, you know a lot of all the possible data that your class can collect ( name , age , sex and location in your example). This means that all clients of your class (may) also know this without increasing the number of connections and dependencies.

My solution is to create an "evaluator" class that encapsulates this logic. An instance of the subclass of this class is created by the client and passed to the data collector during the initial data request; this object is responsible for making the decision (and the message to the "collector") when enough data has been collected.

 #include <string> // The class that decides when enough data has been collected // (Provided to class "collector" by its clients) class evaluator { public: virtual ~evaluator() {}; // Notification callbacks; Returning *this aids in chaining virtual evaluator& name_collected() { return *this; } virtual evaluator& age_collected() { return *this; } virtual evaluator& sex_collected() { return *this; } virtual evaluator& location_collected() { return *this; } // Returns true when sufficient data has been collected virtual bool enough() = 0; }; // The class that collects all the data class collector { public: void collect_data( evaluator& e ) { bool enough = false; while ( !enough ) { // Listen to data source... // When data comes in... if ( /* data is name */ ) { name = /* store data */ enough = e.name_collected().enough(); } else if ( /* data is age */ ) { age = /* store data */ enough = e.age_collected().enough(); } /* etc. */ } } // Data to collect std::string name; int age; char sex; std::string location; }; 

In your example, you wanted a specific client to be able to indicate that a combination of age and sex is sufficient. So, you subclass evaluator as follows:

 class age_and_sex_required : public evaluator { public: age_and_sex_required() : got_age( false ) , got_sex( false ) { } virtual age_and_sex_required& age_collected() override { got_age = true; return *this; } virtual age_and_sex_required& sex_collected() override { got_sex = true; return *this; } virtual bool enough() override { return got_age && got_sex; } private: bool got_age; bool got_sex; }; 

The client passes an instance of this class when requesting data:

 collector c; c.collect_data( age_and_sex_required() ); 

The collect_data method exits and returns when an instance of age_and_sex_required reports that the amount of data collected is "sufficient" and you have not created any logic, knowledge, enumerations, etc. in the collector class. In addition, the logic of what is “enough” is infinitely configurable without further changes to the collector class.

----- EDIT -----

The alternative version will not use a class with methods ..._collected() , but just one (typedef'd) function that takes collector as a parameter and returns boolean :

 #include <functional> typedef std::function< bool( collector const& ) > evaluator_t; 

The code in collector::collect_data(...) will just call

 enough = e( *this ); 

every time a piece of data is collected.

This would eliminate the need for a separate abstract evaluator interface, but add a dependency on the collector class itself, since the object passed as the evaluator_t function would be responsible for checking the state of the collector to evaluate if enough data had been collected (and would require the collector have enough public interface to find out about its status):

 bool age_and_sex_required( collector const& c ) { // Assuming "age" and "sex" are initialized to -1 and 'X' to indicate "empty" // (This could be improved by changing the members of "collector" to use // boost::optional<int>, boost::optional<char>, etc.) return (c.age >= 0) && (c.sex != 'X'); } 
+3
source share

Not sure if this will work for you or not, but because each element may or may not be present, boost::optional come out of sight.

 { boost::optional<string> name; boost::optional<int> age; boost::optional<char> sex; boost::optional<string> location; } 

Then your class can have a bool validate() method that checks for the presence of the required set of elements. It can be a class method or passed as a callback.

+2
source share

You can define a default value for each member saying "I need you."

 static const string required_name = /* your default name */; // ... 

You can also use an integer in the form of a bitmask, which will behave as a set of enumerations.

 typedef int mask_type; static const mask_type name_flag = 0x01; static const mask_type age_flag = 0x02; static const mask_type sex_flag = 0x04; static const mask_type location_flag = 0x08; //... mask_type required = name_flag | age_flag; // need to collect name & age collect(&my_instance, required) // collect and set required values 

Ease of use and lack of overhead than one int :

  • Value is no longer required: required &= ~xx_flag
  • no more values ​​required: bool(required)
  • bool(required & xx_flag) value bool(required & xx_flag)
  • ...
+2
source share

Enums seems like the cleanest way to do this, but I suppose if you would prefer to use short strings with a different character matching each data type. It is not so clean, but it can be easier to debug.

+1
source share

Could you achieve this behavior using a template and an abstract class by doing something like this?

 class SomeAbstract { public: virtual bool getRequired() = 0; virtual void setRequired(bool req) = 0; }; template <class T> class SomeTemplate { T value; bool required; public: TemplateName(T t) { value = t; required = false; } void setRequired(bool req) { required = req; } bool getRequired() { return required; } void setValue(T newValue) { value = newValue; } T getValue() { return value; } }; 

you can declare a list of attributes of the same type.

 SomeTemplate<string> name; SomeTemplate<int> age; SomeTemplate<char> sex; SomeTemplate<string> location; 

Since the template inherits the same type, you can store them in std::vector<SomeAbstract> and treat them the same way.

This is not verified code, and the idea may have some improvements, but I hope you understand my point.

+1
source share

All Articles