I'm using the Model View Presenter (MVP) pattern in MFC that's not hugely relevant to my question but I mention it for context. I have some code that is designed to populate a list control with data from a list.
I have two list boxes which have almost the same code except the type of list is different and the function used to actually put items in the list control is different. So my questions are:
- How can I genericize this member function so that I could pass it an arbitrary list of objects, and a population function (i.e., what AddCustomerToListControl is)?
- Besides genericizing it are there other improvements to be made?
(Note the calls to SetTopIndex are so that the scroll position is remembered.)
void MyDialog::SetCustomerList(const std::list<Customer*>& customers)
{
customersListCtrl_.SetRedraw(FALSE);
int selectedItemIndex = customersListCtrl_.GetNextItem(-1, LVNI_SELECTED);
int topIndex = customersListCtrl_.GetTopIndex();
customersListCtrl_.DeleteAllItems();
if(customers.size() > 0)
{
for(std::list<Customer*>::const_iterator it = customers.begin();
it != customers.end() ; it ++)
{
AddCustomerToListControl(*it);
}
customersListCtrl_.SetItemState(selectedItemIndex,LVIS_SELECTED, LVIS_SELECTED);
}
customersListCtrl_.SetTopIndex(topIndex);
customersListCtrl_.SetRedraw(TRUE);
}
UPDATE
Another detail is the the function AddCustomerToListControl
above needs a reference to the listControl in order to add items to it (e.g., call InsertItem
and SetItemText
)
1 Answer 1
To Generalize it:
class MyDialog
{
template<typename T, typename A>
void SetCustomerList(T const& customers, A action);
....
};
template<typename T, typename A>
void MyDialog::SetCustomerList(T const& customers, A action)
{
.....
// AddCustomerToListControl(*it);
action(*it);
.....
}
// At call point:
SetCustomerList(customers, &AddCustomerToListControl);
Other Changes:
You can use the standard algorithms:
for(std::list<Customer*>::const_iterator it = customers.begin();
it != customers.end() ; it ++)
{
AddCustomerToListControl(*it);
}
Can be replaced with:
std::for_each(customer.begin(), customer.end(), AddCustomerToListControl);
// or in the new code:
std::for_each(customer.begin(), customer.end(), action);
-
\$\begingroup\$ Will I be able to call
size()
orbegin()
andend()
on customers? How does the compiler know T is a list (or container)? \$\endgroup\$User– User2011年10月28日 14:16:30 +00:00Commented Oct 28, 2011 at 14:16 -
\$\begingroup\$ @User: At the call point (see above). When you pass an object it then checks the types of the parameters and works out what T and A should be and then compiles the function using these types. So T can be anything; but it will fail to compile if T does not have the methods size(), begin(), end() or you can not call A like a function. \$\endgroup\$Loki Astari– Loki Astari2011年10月28日 15:33:22 +00:00Commented Oct 28, 2011 at 15:33
-
\$\begingroup\$ So is the way you've defined it better than doing it this way:
void MyDialog::SetCustomerList<T,A>(const std::list<T>& customers, A action)
? I guess more flexible? \$\endgroup\$User– User2011年10月28日 16:01:52 +00:00Commented Oct 28, 2011 at 16:01 -
\$\begingroup\$ @User: Its 6 of one half a dozen of the other. The way I define it allows any type to be used. But if you want to restrict the type to std::list then your way works (and it is often good to restrict the type to things you expect (you can always make it more general later)). \$\endgroup\$Loki Astari– Loki Astari2011年10月28日 16:11:43 +00:00Commented Oct 28, 2011 at 16:11
-
\$\begingroup\$ Thinking about it. It is more customary to pass the iterators:
void MyDialog::SetCustomerList<I,A>(I begin, I end, A action)
but to be honest I would pass the container for this situation. \$\endgroup\$Loki Astari– Loki Astari2011年10月28日 16:13:00 +00:00Commented Oct 28, 2011 at 16:13