当前位置:网站首页>Using C + + template display instantiation to solve the separation of template function declaration and Implementation

Using C + + template display instantiation to solve the separation of template function declaration and Implementation

2020-11-10 11:24:34 goodcitizen

The problem background

Before you start the text , Do some background bedding , It is convenient for readers to understand my engineering requirements . My project is a client message distribution center , After connecting to the message background , The backstage will push some news to me from time to time , I then forward them to other desktop products on this computer for display . Background to ensure that messages can be pushed to the client , It takes a strategy of pushing repeatedly , in other words , Every time I reconnected to the background , The backstage will push me all the news in a period of time 、 Whether or not these messages have been pushed before , If I don't deal with it, I just push it to the product , It may cause the same message to be displayed repeatedly for many times . So , After I received the message , They will be stored in a container in the process , When new news arrives , It will check whether the message has been received in this container first , If there is , No forwarding .

 1 namespace GCM {
 2     class server_msg_t 
 3     {
 4     public:
 5         void dump(char const* prompt); 
 6     
 7         std::string appname; 
 8         std::string uid; 
 9         std::string msgid; 
10         time_t recv_first = 0; 
11         time_t recv_last = 0; 
12         int recv_cnt = 0; 
13     };
14     
15     class WorkEngine
16     {
17     public:
18         WorkEngine();
19         ~WorkEngine();
20     
21     private:
22         // to avoid server push duplicate messages to same client.
23         // note this instance is only accessed when single connection to server arrives message, so no lock needed..
24         std::vector<server_msg_t> m_svrmsgs;
25     };
26 }

 

Above is the simplified code ,m_svrmsgs Members store all the background messages they receive ,server_msg_t It represents a background message ,appname、uid Which instance is used to locate which product to send ;msgid Used to uniquely identify a message ;recv_first、recv_last、recv_cnt The first time the message was received 、 The last time and the number of repetitions . Now a very realistic problem is , I need to serialize these messages to persistent storage , So that the information is still available after the process is restarted . I used it here sqlite database , The relevant code is encapsulated in WorkEngine In the member function of , It's easy to think of a way to declare a function like this :

 1 namespace GCM {
 2     class server_msg_t 
 3     {
 4     public:
 5         void dump(char const* prompt); 
 6     
 7         std::string appname; 
 8         std::string uid; 
 9         std::string msgid; 
10         time_t recv_first = 0; 
11         time_t recv_last = 0; 
12         int recv_cnt = 0; 
13     };
14     
15     class WorkEngine
16     {
17     public:
18         WorkEngine();
19         ~WorkEngine();
20     
21     protected:
22         int db_store_server_msg (std::vector<server_msg_t> const& vec); 
23         int db_fetch_server_msg (std::vector<server_msg_t> & vec);
24     
25     private:
26         // to avoid server push duplicate messages to same client.
27         // note this instance is only accessed when single connection to server arrives message, so no lock needed..
28         std::vector<server_msg_t> m_svrmsgs;
29     };
30 }
31         

 

image line 22-23 As shown , Use it directly std::vector<server_msg_t> This container as a parameter ( Some people may think that I'm overstepping , Access directly in the function m_svrmsgs It's just the members , Why pass through parameters ? Maybe this example is not obvious , But there are some cases where containers exist as local variables rather than as member variables , Here are some simplifications for illustrative purposes ). But I think it's too rigid , What if I change the container later , Is it necessary to change here ? Maybe it's generic algorithms that look too much , It's not enough to write like this “ Universal ”. But if it's written like this , Or change the soup without changing the dressing :

int db_store_server_msg (std::vector<server_msg_t>::iterator beg, std::vector<server_msg_t>::iterator end); 

 

Reference standard library std::copy Algorithm , Transform it , It turns out to be like this :

template <class InputIterator>
int db_store_server_msg(InputIterator beg, InputIterator end); 

 

It's called a member function template , Or member template function , Or template member function …… I don't know , It's a member function anyway + template function . It can be written in this way :

 1 namespace GCM {
 2     template <class InputIterator>
 3     int WorkEngine::db_store_server_msg(InputIterator beg, InputIterator end)
 4     {
 5         int ret = 0, rowid = 0; 
 6         qtl::sqlite::database db(SQLITE_TIMEOUT);
 7     
 8         try
 9         {
10             db.open(get_db_path().c_str(), NULL);
11             writeInfoLog("open db for store server msg OK");
12     
13             db.begin_transaction();
14     
15             for (auto it = beg; it != end; ++it)
16             {
17                 // 1th, insert or update user info
18                 rowid = db.insert_direct("replace into server_msg (appname, uid, msgid, first_recv, last_recv, count) values (?, ?, ?, ?, ?, ?);", 
19                     it->appname, it->uid, it->msgid, it->recv_first, it->recv_last, it->recv_cnt);
20     
21                 ret++; 
22             }
23     
24             db.commit();
25             db.close();
26             writeInfoLog("replace into %d records", ret); 
27         }
28         catch (qtl::sqlite::error &e)
29         {
30             writeInfoLog("manipute db for store server msg error: %s", e.what());
31             db.rollback();
32             db.close();
33             return -1;
34         }
35     
36         return ret; 
37     }
38 }

 

You can see , The core code is to traverse the iterator interval (line 15). The caller is also very concise :

db_store_server_msg(m_svrmsgs.begin(), m_svrmsgs.end()); 

 

One line fix , It looks like it's done , There's no difficulty , So what is the point of this article ? take it easy , The real difficulty is recovering data from the database . First of all, we can't use iterators directly , Because now we're going to insert elements into the container , Iterators can only traverse elements , It doesn't help at all . But I believe that readers must have seen code like this :

 1 int main (void)
 2 {
 3     int arr[] = { 1, 3, 5, 7, 11 }; 
 4     std::vector vec; 
 5     std::copy (arr, arr + sizeof (arr) / sizeof (int), std::back_inserter(vec)); 
 6     for (auto it = vec.begin (); it != vec.end (); ++ it) 
 7         printf ("%d\n", *it); 
 8 
 9     return 0; 
10 }

 

To insert elements at the end of the container , The standard library algorithm relies on back_inserter This east east . So naturally I thought , Can we state here that back_inserter As an input parameter ? Like this :

template <class OutputIterator>
int db_fetch_server_msg(OutputIterator it);

 

Template implementation is written like this :

 1 namespace GCM {
 2     template <class OutputIterator>
 3     int WorkEngine::db_fetch_server_msg(OutputIterator it)
 4     {
 5         int ret = 0;
 6         qtl::sqlite::database db(SQLITE_TIMEOUT);
 7     
 8         try
 9         {
10             db.open(get_db_path().c_str(), NULL);
11             writeInfoLog("open db for fetch server msg OK");
12     
13             db.query("select appname, uid, msgid, first_recv, last_recv, count from server_msg", 
14                 [&ret, &it](std::string const& appname, std::string const& uid, std::string const& msgid, time_t first_recv, time_t last_recv, int count) {
15                     server_msg_t sm; 
16                     sm.appname = appname; 
17                     sm.uid = uid; 
18                     sm.msgid = msgid; 
19                     sm.recv_first = first_recv; 
20                     sm.recv_last = last_recv; 
21                     sm.recv_cnt = count; 
22                     *it = sm; 
23                     ++ret; 
24             }); 
25     
26             db.close();
27             writeInfoLog("query %d records", ret);
28         }
29         catch (qtl::sqlite::error &e)
30         {
31             writeInfoLog("manipute db for store server msg error: %s", e.what());
32             db.close();
33             return -1;
34         }
35     
36         return ret;
37     }
38 }

 

In fact, the core is a sentence to back_inserter Assignment statement for (line 22). The caller does the same thing in one line :

db_fetch_server_msg (std::back_inserter(m_svrmsgs)); 

 

The separation of template declaration and template implementation

The above code can be compiled normally , But the premise is that the template implementation and template call are in the same file . Considering this class, there was a lot of logic before , I decided that the content related to the database , Transfer to a new file (engine_db.cpp), To reduce the amount of code in a single file . The adjusted file structure is as follows :

+ engine.h: WorkEngine  Statement 
+ engine.cpp:WorkEngine  Realization  ( contain  engine.h)
+ engine_db.cpp:WorkEngine::db_xxx  Template implementation  ( contain  engine.h)

 

recompile , Reported a link error :

1>workengine.obj : error LNK2001:  Unresolved external symbols  "protected: int __thiscall GCM::WorkEngine::db_fetch_server_msg<class std::back_insert_iterator<class std::vector<class GCM::server_msg_t,class std::allocator<class GCM::server_msg_t> > > >(class std::back_insert_iterator<class std::vector<class GCM::server_msg_t,class std::allocator<class GCM::server_msg_t> > >)" (??$db_fetch_server_msg@V?$back_insert_iterator@V?$vector@Vserver_msg_t@GCM@@V?$allocator@Vserver_msg_t@GCM@@@std@@@std@@@std@@@WorkEngine@GCM@@IAEHV?$back_insert_iterator@V?$vector@Vserver_msg_t@GCM@@V?$allocator@Vserver_msg_t@GCM@@@std@@@std@@@std@@@Z)

 

Obviously, it is caused by the failure to find the corresponding link when calling the template . You need to use “ Template display instantiation ” stay engine_db.cpp The file forces the template to generate the corresponding code entity , Come and join us engine.cpp Link with the call point in the . You need to add the following two lines of code at the beginning of the file :

using namespace GCM;
template int WorkEngine::db_fetch_server_msg<std::back_insert<std::vector<server_msg_t> > >(std::back_insert<std::vector<server_msg_t> >);

 

Notice the syntax of template member functions showing instantiation , I looked it up 《cpp primer》, The format is :

template return_type CLASS::member_func<type1, type2, ……> (type1, type2, ……); 

 

Corresponding to the above statement , Is the use of std::back_insert<std::vector<server_msg_t> > Replace the original OutputIterator type , To tell the compiler to display the generation of such a function template instance . Note that the same type should be written twice , One is the function template parameter , One time it's a function parameter . However, the display instantiation syntax failed to compile :

1>engine_db.cpp(15): error C2061:  Grammar mistakes :  identifier “back_inserter”
1>engine_db.cpp(15): error C2974: 'GCM::WorkEngine::db_fetch_server_msg' :  Templates   about  'OutputIterator' Is an invalid parameter , Should be type 
1>          f:\gdpclient\src\gcm\gcmsvc\workengine.h(137) :  See “GCM::WorkEngine::db_fetch_server_msg” Statement of 
1>engine_db.cpp(15): error C3190:  With the template parameters provided “int GCM::WorkEngine::db_fetch_server_msg(void)” No “GCM::WorkEngine” An explicit instantiation of any member function of 
1>engine_db.cpp(15): error C2945:  Explicit instantiation does not refer to template class specialization 

 

puzzled . Went out for a walk , A little fresh air , The brain suddenly came into being : There was a long list of link errors before , Use the type in there directly , Should be able to compile ! Do as you say , So here's a long list of instantiation declarations :

template int GCM::WorkEngine::db_fetch_server_msg<class std::back_insert_iterator<class std::vector<class GCM::server_msg_t,class std::allocator<class GCM::server_msg_t> > > >(class std::back_insert_iterator<class std::vector<class GCM::server_msg_t,class std::allocator<class GCM::server_msg_t> > >)

 

Too much is —— Actually passed the compilation ! Take a closer look at this long list of type declarations , It seems that it's just vector It's just unfolding , I use “ Condensed version ” Of vector Again, try to find out what's changed :

template int GCM::WorkEngine::db_fetch_server_msg<std::back_insert_iterator<std::vector<server_msg_t> > >(std::back_insert_iterator<std::vector<server_msg_t> >);

 

  It's actually passed . It looks like it's just using back_insert_iterator Instead of back_inserter Just fine ,back_insert_iterator What a ghost again ? see back_inserter Definition , Here are the findings :

1 template<class _Container> inline back_insert_iterator<_Container> back_inserter(_Container& _Cont)
2 {    // return a back_insert_iterator
3     return (_STD back_insert_iterator<_Container>(_Cont));
4 }

 

Looks like back_inserter It's a return back_insert_iterator Template function of type , And std::make_pair(a,b) and   std::pair <A,B> It's like , Because this is a type , So it can't be transmitted directly back_inserter This function gives the display instantiation declaration . good , So far I'm not , We did it with a inserter Or two iterator Parameters instead of clumsy container parameters 、 And can declare 、 call 、 Split it in three different files , It's perfect . There is a fly in the ointment , Template display instantiation is a bit tedious , Use here typedef Define the type to instantiate , Make the above sentence clearer :

typedef std::back_insert_iterator<std::vector <server_msg_t> > inserter_t;
template int WorkEngine::db_fetch_server_msg<inserter_t>(inserter_t);

 

Empathy , Yes db_store_server_msg Make the same transformation :

typedef std::vector <std::string, server_msg_t>::iterator iterator_t;
template int WorkEngine::db_store_server_msg<iterator_t>(iterator_t, iterator_t);

 

Is this more perfect ?

Use map Instead of vector

In use , Discover the use of map It is faster and more convenient to query whether the message is already in the container , So I decided to change the message container definition as follows :

std::map<std::string, server_msg_t> m_servmsgs;

 

among map Of value Part of the same as before , To increase the key Part of it is msgid. After this change , Use... When traversing "it->second." Instead of "it->"; You need to use “*it = std::make_pair (sm.msgid, sm)” Instead of “*it = sm”. Make the above changes , I found that the program still failed to compile . After some investigation , It turned out to be back_inserter It doesn't fit map Containers . because back_inserter Corresponding back_insert_iterator stay = The container will be called in the operator push_back Interface , And this interface is only vector、list、deque Several containers support ,map Is not supported . What shall I do? , Fortunately, some kind-hearted people have written it map The inserter of —— map_inserter:

 1 #pragma once
 2 
 3 namespace std
 4 {
 5     template <class _Key, class _Value, class _Compare>
 6     class map_inserter {
 7 
 8     public:
 9         typedef std::map<_Key, _Value, _Compare> map_type;
10         typedef typename map_type::value_type value_type;
11 
12     private:
13         map_type &m_;
14 
15     public:
16         map_inserter(map_type &_m)
17             : m_(_m)
18         {}
19 
20     public:
21         template <class _K, class _V, class _Cmp>
22         class map_inserter_helper {
23         public:
24             typedef map_inserter<_K, _V, _Cmp> mi_type;
25             typedef typename mi_type::map_type map_type;
26             typedef typename mi_type::value_type value_type;
27 
28             map_inserter_helper(map_type &_m)
29                 :m_(_m)
30             {}
31 
32             const value_type & operator= (const value_type & v) {
33                 m_[v.first] = v.second;
34                 return v;
35             }
36         private:
37             map_type &m_;
38         };
39 
40         typedef map_inserter_helper<_Key, _Value, _Compare> mi_helper_type;
41         mi_helper_type operator* () {
42             return mi_helper_type(m_);
43         }
44 
45         map_inserter<_Key, _Value, _Compare> &operator++() {
46             return *this;
47         }
48 
49         map_inserter<_Key, _Value, _Compare> &operator++(int) {
50             return *this;
51         }
52 
53     };
54 
55     template <class _K, class _V, class _Cmp>
56     map_inserter<_K, _V, _Cmp> map_insert(std::map<_K, _V, _Cmp> &m) {
57         return map_inserter<_K, _V, _Cmp>(m);
58     }
59 }; 

 

I copied this code from the Internet , Please refer to the link below for details :std::map Of inserter Realization . unfortunately , This code “ disability ” 了 , I don't know it's the author who stole the chain 、 Still not input the complete reason , This code has some inherent grammatical defects , It doesn't even compile , In my unremitting “ Cerebral repair ” In the process , The missing parts have been made up with highlights , Ladies and gentlemen, you can enjoy it directly ~

In particular , The most technical deficiency occurs in line 37 A reference to , If you don't add this , Although it can be compiled , But in the process of operation ,inserter Not to map Insert elements in , Will result in an empty read from the database map. I've been trying to find the original text of this article , But nothing , For the Internet dissemination process found such a donkey head horse mouth error event , I feel very sad ( Although I don't quite understand , But you can't pit me either )……

Okay , Back to the point , With map_inserter after , So we can say that :

typedef std::map_inserter<std::string, server_msg_t, std::less<std::string> > inserter_t;
template int WorkEngine::db_fetch_server_msg<inserter_t>(inserter_t);

 

For this map_inserter Realization , We need to deliver map Three template parameters of , instead of map This parameter itself , I'm not sure it's progress 、 It's a kind of retrogression , Anyway, this one map_inserter It's a little weird , Not encapsulated as map_insert_iterator + map_inserter In the form of , There is still a difference between the standard library and the implementation level , Let's see . The caller also needs to make some fine-tuning :

db_fetch_server_msg(std::map_inserter<std::string, server_msg_t, std::less <std::string> >(m_svrmsgs));

 

have a look , There's no simplicity in standard library implementation , Is it a fake product ~ Fortunately, we've encapsulated inserter_t type , It can be rewritten like this :

db_fetch_server_msg(inserter_t(m_svrmsgs));

 

It's much simpler . Now let's look at the file composition of the project :

+ map_inserter.hpp: map_inserter  Statement + Realization 
+ engine.h: WorkEngine  Statement  ( contain  map_inserter.hpp)
+ engine.cpp:WorkEngine  Realization  ( contain  engine.h)
+ engine_db.cpp:WorkEngine::db_xxx  Template implementation  ( contain  engine.h)
……

 

Here, to reduce the complexity , take map_inserter Put it in the header file for sharing , Similar to the way standard library header files are used .

Use the normal template function instead of the class member template function

At the end of this article , Let's go back to the two member template functions in the above example , It is found that they are not used by other members of the class , In fact, they can be separated into two ordinary template functions to call , For example, change it to this :

 1 namespace GCM {
 2     class server_msg_t 
 3  {
 4 public:
 5 void dump(char const* prompt); 
 6 
 7 std::string appname; 
 8 std::string uid; 
 9 std::string msgid; 
10 time_t recv_first = 0; 
11 time_t recv_last = 0; 
12 int recv_cnt = 0; 
13  };
14 
15 class WorkEngine
16  {
17 public:
18  WorkEngine();
19 ~WorkEngine();
20 
21 private:
22 // to avoid server push duplicate messages to same client.
23 // note this instance is only accessed when single connection to server arrives message, so no lock needed..
24 std::vector<server_msg_t> m_svrmsgs;
25  };
26 
27 template <class InputIterator>
28 int db_store_server_msg(InputIterator beg, InputIterator end); 
29 template <class OutputIterator>
30 int db_fetch_server_msg(OutputIterator it);
31 
32 typedef std::map <std::string, server_msg_t>::iterator iterator_t;
33 typedef std::map_inserter<std::string, server_msg_t, std::less<std::string> > inserter_t;
34 }

 

Move template function declaration from class to out of class (line 27-30), At the same time to modify engine_db.cpp The definition and display of instantiation statements of two classes in , Remove class restrictions (WorkEngine::):

template int db_fetch_server_msg<inserter_t>(inserter_t);
template int db_store_server_msg<iterator_t>(iterator_t, iterator_t);

 

There is no need to modify at the caller . Error is reported when compiling again :

1>engine_db.cpp(16): warning C4667: “int GCM::db_fetch_server_msg(GCM::inserter_t)”:  There is no function template defined that matches the mandatory instantiation 
1>engine_db.cpp(17): warning C4667: “int GCM::db_store_server_msg(GCM::iterator_t,GCM::iterator_t)”:  There is no function template defined that matches the mandatory instantiation 
1>      Creating library  F:\gdpclient\src\gcm\Release\gcmsvc.lib  And the object  F:\gdpclient\src\gcm\Release\gcmsvc.exp
1>workengine.obj : error LNK2001:  Unresolved external symbols  "int __cdecl GCM::db_fetch_server_msg<class std::map_inserter<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class GCM::server_msg_t,struct std::less<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > > > >(class std::map_inserter<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,class GCM::server_msg_t,struct std::less<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > > >)" (??$db_fetch_server_msg@V?$map_inserter@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@Vserver_msg_t@GCM@@U?$less@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@2@@std@@@GCM@@YAHV?$map_inserter@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@Vserver_msg_t@GCM@@U?$less@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@2@@std@@@Z)
1>workengine.obj : error LNK2001:  Unresolved external symbols  "int __cdecl GCM::db_store_server_msg<class std::_Tree_iterator<class std::_Tree_val<struct std::_Tree_simple_types<struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const ,class GCM::server_msg_t> > > > >(class std::_Tree_iterator<class std::_Tree_val<struct std::_Tree_simple_types<struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const ,class GCM::server_msg_t> > > >,class std::_Tree_iterator<class std::_Tree_val<struct std::_Tree_simple_types<struct std::pair<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const ,class GCM::server_msg_t> > > >)" (??$db_store_server_msg@V?$_Tree_iterator@V?$_Tree_val@U?$_Tree_simple_types@U?$pair@$$CBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@Vserver_msg_t@GCM@@@std@@@std@@@std@@@std@@@GCM@@YAHV?$_Tree_iterator@V?$_Tree_val@U?$_Tree_simple_types@U?$pair@$$CBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@Vserver_msg_t@GCM@@@std@@@std@@@std@@@std@@0@Z)

 

The first two warning It's because after a member function becomes a normal function , The display instantiation needs to be placed behind the function implementation , Let's adjust these two statements to the end of the file . For the next two links error, puzzled , And then I used a very simple test Template functions do experiments , Discovery is caused by the namespace , You need to add a namespace qualification before the definition and display of each function instantiation statement (GCM::):

template int GCM::db_fetch_server_msg<inserter_t>(inserter_t);
template int GCM::db_store_server_msg<iterator_t>(iterator_t, iterator_t);

 

You can see , Class member template functions and ordinary template functions are still quite different , Because the class itself is a namespace , Its appearance simplifies the addressing of member functions .

Conclusion

In fact, this article explains a general way to pass iterator Read the container 、 adopt inserter How to insert container elements , It's a way to pass on the container itself directly “ grace ” not a few , Although it can't be realized 100% Seamless switching containers , But it also provides great flexibility . In particular, it also studies how to declare and implement the template functions implemented in this way in different files , To achieve the purpose of decoupling code , It has strong practicability . Of course , This is just the template instantiation method , If you encounter different templates TYPE If you need to use a different function implementation , You may also encounter template specific syntax ( Including full specialization and partial specialization ), That's going to increase the complexity , There is no further exploration here .

Reference resources

[1]. C++ 11 Lambda expression

[2]. std::map Of inserter Realization

[3]. C++ The separation of declaration and implementation of template class ( template instantiation )

[4]. C++ How to compile function templates

[5]. c++ Function template declaration is separated from definition

[6]. C++ Template function template instantiation and materialization

[7]. C++ Function templates Instantiation and materialization

[8]. C++ Implicit instantiation of templates 、 Explicit Instantiation 、 Implicit call 、 Show call and template specialization details

[9]. c++ Template function declaration and definition separation

[10]. C++ Template programming : How to realize the separation of declaration and definition for non general template functions

版权声明
本文为[goodcitizen]所创,转载请带上原文链接,感谢