once in a project encountered a very simple problem, but the moment can not be used in the ordinary way to a very good solution, and finally through the use of C + + template, through the traits relatively clever to overcome the problem. This article mainly wants to reproduce the problem occurrence, several solutions, and finally how to solve the process, perhaps finally the solution is not the best solution, but at least the individual think from the discovery to thinking to solve to improve, this is a very good way to help personal growth, so by memory want to record it, share to everyone.
describe the problem first, the project has such an interface class will be exposed to external use, interface class definitions such as the following (class method name and description of the problem is not related to the content will be changed, omitted or deleted):
Class Icontainer{public: virtual RESULT Insert (const std::string& key, const exportdata& data) = 0; Virtual RESULT Delete (const std::string& key) = 0; Virtual RESULT Find (const std::string& key, exportdata& data) = 0;};
from the content and the name very easy to see that the interface is nothing more than a container, can be increased, change, check operation, too simple, this interface class will have what kind of problem? From the method of the interface class, there is a data type ExportData in the Insert and Find methods , respectively, as input and output, and now there is this requirement: 1. ExportData need to support only integer (long), floating-point (double), String, and binary (void*, size) 4 types of operations (int or float not supported) 2. ExportData need to consider the size of the structure to minimize space redundancy 3. Even if you operate on the 4 different data types above, or if you want to get or set real data from ExportData, the method used can be unified 4. When the caller tries to use a data type other than the above 4 types, the caller is able to know the type mismatch by returning an error
What do you do when the description of the requirements is complete? How to define and implement ExportData? is not very easy, the first feeling can solve the problem immediately, and there are n ways.
In the first scenario, the GetData and SetData methods are defined for ExportData, and the method is overloaded for 4 types, with code such as the following:
Class Exportdata{public: long GetData () { return m_ldata; } void SetData (Long data) { m_ldata = data; } String GetData () { return m_strdata; } void SetData (String data) { m_strdata = data; } Overload the other typesprivate: long m_ldata; string M_strdata; ... overload the other types};
immediately discovered the problem, first the GetData method simply cannot be overloaded by the return value, but immediately thought we could fix the problem with a slight modification:
void GetData (long& data) { data = M_ldata;} void SetData (Long data) { m_ldata = data;} void GetData (string& data) { data = M_strdata;} void SetData (const string& data) { m_strdata = data;}
But there is still a careful look at the problem, did not meet the requirements of 2, even if the user is using integer data, the other three data types in the structure is still present, internal data redundancy. wouldn't it be possible to use class templates to solve the problem? The code looks like the following:
Template<typename t>class exportdata{public: T GetData () { return m_ldata; } void SetData (T data) { m_data = data; } Private: T m_data;};
so simple, so there is no redundancy, but this is all just to support the assignment operation of the type are supported, do not meet the requirements of 1. A lot of people at this time will certainly think, then use the next traits can not solve the problem? (about traits can participate in the traits of C + + template)
Template<typename t>class exportdata{public: RESULT GetData (t& data) { return ERROR; } RESULT SetData (const t& data) { return ERROR; }}; Template<>class exportdata<long>{public: RESULT GetData (long& data) { data = m_data; return OK; } RESULT SetData (const long& data) { m_data = data; return OK; } Private: long m_data;}; Template<>class Exportdata<double>, ...//Just like the implementation of Longtemplate<>class Exportdata<string>, ...//Just like the implementation of Longtemplate<>class exportdata<binary> ...//Just like the implementation of long
Meet the requirements of 1 only support four types, meet the requirements 2 No redundancy, meet the requirements of 3 unified call form, but for the problem of demand 4, because when you use int or float still support, that is, only the data can be implicitly converted, Do not return an error prompt to the caller, then improve it:
Template<typename t>struct typetraits{ static const data_type field_type = type_unsupported;}; Template<>struct typetraits<std::string>{ static const data_type field_type = Type_utf8;}; Template<>struct typetraits<long>{ static const data_type field_type = Type_ineger;}; Template<>struct typetraits<double>{ static const data_type field_type = type_real;}; Template<>struct typetraits<binary>{ static const data_type field_type = type_binary;};
The above first through the traits method to obtain a can be used to infer whether we support the data type of way, the establishment is not supported, is not supported, inferred way such as the following :
Typetraits<long>::field_type = = type_unsupported
then ExportData such as the following implementations:
Template<typename t>class exportdata{public: RESULT GetData (t& data) { return ERROR; } RESULT SetData (const t& data) { return ERROR; }}; Template<>class exportdata<long>{public: RESULT GetData (long& data) { if (typetraits< Long>::field_type = = type_unsupported) { return ERROR; } data = m_data; return OK; } RESULT SetData (const long& data) { m_data = data; return OK; } Private: long m_data;};
now only these four types will be supported, other types will return errors, it seems that all the needs are supported, then this is the last solution? No, no! we ignored where ExportData was used, and it was used in the virtual method of the interface class, and because of the features of C + + compilation, the C + + language itself does not support the virtual function itself or the template function. That is to say, the definition of the interface class is only (as for why C + + does not support, this is related to C + + compilation, here does not explain, if there is such a question can be in reply to my message):
Class Icontainer{public: template<typename t> virtual RESULT Insert (const std::string& key, const exportdata<t>& data) = 0; Virtual RESULT Delete (const std::string& key) = 0; Template<typename t> Virtual RESULT Find (const std::string& key, exportdata<t>& data) = 0;};
IContainer itself is not just related to a type, that is, it is impossible to define IContainer as a template class. What do we do? because the virtual function cannot be a template function at the same time, so the ExportData class cannot be defined as a template class, can you try to fix the get and set methods of the ExportData class as template methods? There is still a problem with this, because you cannot make the template class, the data of that type of member variable, how to support 4 types? The answer is to deal with the type-independent binary data, that is, the first address of the data and the size of the data, which are copied to the specified type by memcpy at Get and set, based on the current type. Look at the code, the definition of typetraits is the same as above, here is not repeated:
Class Exportdata{public:exportdata (): _data (NULL), _size (0) {} exportdata (const exportdata& data) { _data = NULL; _size = 0; Assigndata (Data._data, data._size); _type = Data._type; } ~exportdata () {if (_data) {delete[] _data; _data = NULL; }} exportdata& operator= (const exportdata& data) {This->assigndata (Data._data, data. _size); This->_type = Data._type; return *this; Template<typename t> RESULT SetData (const t& data) {if (Typetraits<t>::field_type = = T ype_unsupported) {return ERROR; } assigndata (const char*) &data, sizeof (T)); _type = typetraits<t>::field_type; return OK; } template<> RESULT setdata<std::string> (const std::string& data) {Assigndata (Data.c_str (), data.size ()); _type = Type_utf8; return OK; } template<> RESULT setdata<binary> (const binary& data) {assigndata (data). GETBLOBADDR (), data. GetSize ()); _type = Type_blob; return OK; } template<typename t> RESULT GetData (t& data) const {if (typetraits<t>::field_t ype = = Type_unsupported | | _data = = NULL | | Typetraits<t>::field_type! = _type) {return ERROR; } memcpy (&data, _data, _size); return OK; } template<> RESULT getdata<std::string> (std::string& data) const {if (Type_utf8 ! = _type | | _data = = NULL) {data = ""; return ERROR; } data.assign (_data, _size); return OK; } template<> RESULT getdata<binary> (binary& data) const {if (Type_blob! = _type || _data = = NULL) {data. Setblobdata (NULL, 0); return ERROR; } data. Setblobdata (_data, _size); return OK; }private:void assigndata (const char* data, unsigned int size) {if (_data) {delet E[] _data; _data = NULL; } _size = size; _data = new Char[size]; memcpy (_data, data, _size); } char* _data; unsigned long _size; Data_type _type;};
This is the solution at the time, can be considered to meet the previous 4 points of demand, called when the code such as the following:
ExportData data; RESULT res = Ok;res = data. Setdata<string> ("Datatest"); assert (ok = = res); string str;res = data. Getdata<string> (str); assert (OK = = res); res = data. Setdata<long> (111); long ldata = 0;res = data. Getdata<long> (Ldata); assert (OK = = res);
I'm sure there's a better workaround, such as being able to try to prompt at compile time by returning an error when the hint type is not supported, rather than when it is executed. If there is a better solution, you are welcome to discuss it together.
The experience of utilizing C + + templates (traits) in the C + + template Tour Project