В нескольких словах: как я могу передать различные поля из пользовательского класса в одну функцию?
Теперь в деталях:
у меня есть std::vector
содержащий класс, например CustomClass
из которого я должен извлечь результат из поля из этого класса по некоторым критериям, которые являются полями в этом классе, и каким-то образом объединить эти данные.
Моим первым подходом к этой проблеме было использование функции, которая принимает в качестве параметра std::vector
класса для того, чтобы извлечь данные и вернуть std:map
, Ключом на этой карте является тип критериев, по которым должны быть объединены данные, а значение int
с объединенными данными от всех членов этого вектора.
Проблема в том, что критерий не только один — в качестве критериев можно использовать более одного поля из этого класса (для удобства все критерии std::string
, если их нет — я мог бы сделать функцию шаблонной).
Самый простой способ для меня сейчас — создать десятки функций с практически одинаковым кодом, и каждая из них извлечет из этого класса простое конкретное поле. Однако изменения могут потребовать аналогичных изменений для всех десятков функций, что может быть головной болью при обслуживании. Но на этом этапе я не могу думать, как передать одной функции поле из этого класса …
Вот пример кода из этого класса:
// this is the class with data and criteria
class CustomClass
{
public:
std::string criteria1;
std::string criteria2;
std::string criteria3;
//... and others criteria
int dataToBeCombined;
// other code
};
// this is one of these functions
std::map<std::string, int> getDataByCriteria1(std::vector<CustomClass> aVector)
{
std::map<std::string, int> result;
foreach(CustomClass anObject in aVector)
{
if(result.find(anObject.criteria1)==result.end()) // if such of key doesn't exists
{
result.insert(std::make_pair(anObject.criteria1, anObject.dataToBeCombined));
}
else
{
// do some other stuff in order to combine data
}
}
return result;
}
и аналогичным образом я должен сделать другие функции, которые должны работать с CustomClass::criteria2
, CustomClass::criteria3
, так далее.
Я подумал сделать эти критерии в одном массиве и передать этой функции только количество критериев, но этот класс будет использоваться другими для других целей, и поля должны быть легко читаемыми, так что это не вариант ( т.е. настоящие имена не criteria1
, criteria2
и т. д., но имеют описательный характер).
Кто-нибудь с идеями?
РЕДАКТИРОВАТЬ: Кто-то направил мой вопрос на «C ++ те же параметры функции с разным типом возвращаемых данных», который, очевидно, сильно отличается — функция в моем случае каждый раз возвращает один и тот же тип, просто параметры, которые он принимает, должны быть различными полями из класса.
Вы можете использовать функцию для извлечения поля, такого как
std::string extractFiled(const CustomClass &object, int which) {
switch (which) {
case 1:
return object.criteria1;
case 2:
return object.criteria2;
case 3:
return object.criteria3;
default:
return object.criteria1;
}
}
а также getDataByCriteria
добавьте аргумент, чтобы указать, какой файл использовать.
Или вы можете просто использовать макрос для реализации getDataByCriteria
,
Вы можете использовать указатель на член. Объявить аргумент std::string CustomClass::*pField
в вашей функции, передать его с &CustomClass::criteriaN
, получить к нему доступ anObject.*pField
,
Смотрите больше по теме: Указатели на данные членов.
Если все «критерии» относятся к одному и тому же типу, я не вижу элегантного решения, но вы можете каким-то образом «перечислить» их и использовать их число.
Например, вы можете объявить шаблон getVal()
метод в CustomClass
в этом случае
template <int I>
const std::string & getVal () const;
и реализовать их, число за номером, критерии за критериями, таким образом (вне тела класса)
template <>
const std::string & CustomClass::getVal<1> () const
{ return criteria1; }
template <>
const std::string & CustomClass::getVal<2> () const
{ return criteria2; }
template <>
const std::string & CustomClass::getVal<3> () const
{ return criteria3; }
Теперь вы можете преобразовать getDataByCriteria1()
в шаблонной функции getDataByCriteria()
в этом случае
template <int I>
std::map<std::string, int> getDataByCriteria (std::vector<CustomClass> aVector)
{
std::map<std::string, int> result;
for (const auto & cc : aVector)
{
if ( result.find(cc.getVal<I>()) == result.end()) // if such of key doesn't exists
{
result.insert(std::make_pair(cc.getVal<I>(), cc.dataToBeCombined));
}
else
{
// do some other stuff in order to combine data
}
}
return result;
}
и называть это так
auto map1 = getDataByCriteria<1>(ccVec);
auto map2 = getDataByCriteria<2>(ccVec);
auto map3 = getDataByCriteria<3>(ccVec);
— РЕДАКТИРОВАТЬ: добавлено решение (только C ++ 14) для различных типов критериев —
Немного иначе, если «критерии» бывают разных типов.
Решение работает, но в C ++ 14, благодаря auto
а также decltype()
,
Например, если
std::string criteria1;
int criteria2;
long criteria3;
Вы можете объявить getVal()
с auto
template <int I>
const auto & getVal () const;
и определить (с auto
) все версии getVal()
template <>
const auto & CustomClass::getVal<1> () const
{ return criteria1; }
template <>
const auto & CustomClass::getVal<2> () const
{ return criteria2; }
template <>
const auto & CustomClass::getVal<3> () const
{ return criteria3; }
и объединение auto
с decltype()
Вы можете изменить getDataByCriteria()
в этом случае
template <int I>
auto getDataByCriteria (std::vector<CustomClass> aVector)
{
std::map<decltype(aVector[0].getVal<I>()), int> result;
for (const auto & cc : aVector)
{
if ( result.find(cc.getVal<I>()) == result.end()) // if such of key doesn't exists
{
result.insert(std::make_pair(cc.getVal<I>(), cc.dataToBeCombined));
}
else
{
// do some other stuff in order to combine data
}
}
return result;
}
Использование функции остается прежним (благодаря auto
снова)
auto map1 = getDataByCriteria<1>(ccVec);
auto map2 = getDataByCriteria<2>(ccVec);
auto map3 = getDataByCriteria<3>(ccVec);
ps .: осторожно: код не проверен
p.s.2: извините за мой плохой английский
Вы пометили его как C ++ 11, так что используйте шаблоны с переменными числами.
class VariadicTest
{
public:
VariadicTest()
{
std::map<std::string, int> test1 = getDataByCriteria(testValues, criteria1);
std::map<std::string, int> test2 = getDataByCriteria(testValues, criteria2);
std::map<std::string, int> test3 = getDataByCriteria(testValues, criteria1, criteria2);
std::map<std::string, int> test4 = getDataByCriteria(testValues, criteria1, criteria3);
}
private:
std::string criteria1 = { "Hello" };
std::string criteria2 = { "world" };
std::string criteria3 = { "." };
std::vector<CustomClass> testValues = { {"Hello",1}, {"world",2},{ "!",3 } };
template<typename T> std::map<std::string, int> getDataByCriteria(std::vector<CustomClass> values, T criteria)
{
std::map<std::string, int> result;
//do whatever is needed here to filter values
for (auto v : values)
{
if (v.identifier == criteria)
{
result[values[0].identifier] = values[0].value;
}
}
return result;
}
template<typename T, typename... Args> std::map<std::string, int> getDataByCriteria(std::vector<CustomClass> values, T firstCriteria, Args... args)
{
std::map<std::string, int> result = getDataByCriteria(values, firstCriteria);
std::map<std::string, int> trailer = getDataByCriteria(values, args...);
result.insert(trailer.begin(), trailer.end());
return result;
}
};
Вы не указываете фактические операции, которые должны быть выполнены при различных условиях критериев, которые встречаются, поэтому трудно сказать, сколько они фактически могут быть объединены.
Вот возможное решение с использованием std::accumulate()
STL наряду с некоторыми дополнительными функциями. Этот пример был скомпилирован с Visual Studio 2015.
Этот подход будет иметь смысл, если большая часть функциональности может быть объединена в достаточно небольшую функцию накопления, поскольку большинство критериев обрабатываются одинаково. Или вы могли бы иметь accumulate_op()
Функция вызывает другие функции для особых случаев, обрабатывая сам общий случай.
Вы можете принять это как начало и внести соответствующие изменения.
Одной из таких модификаций может быть избавление от использования std::map
поддерживать состояние. Так как при использовании этого подхода вы бы перебрали std::vector
делая накопление на основе критериев, я не уверен, что вам даже нужно будет использовать std::map
помнить что-нибудь, если вы накапливаете, как вы идете.
// map_fold.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <numeric>
// this is the class with data and criteria
class CustomClass
{
public:
CustomClass() : dataToBeCombined(0) {}
std::string criteria1;
std::string criteria2;
std::string criteria3;
//... and others criteria
int dataToBeCombined;
// other code
};
// This is the class that will contain the results as we accumulate across the
// vector of CustomClass items.
class Criteria_Result {
public:
Criteria_Result() : dataToBeCombined(0) {}
CustomClass myCriteria;
std::map<std::string, int> result1;
std::map<std::string, int> result2;
std::map<std::string, int> result3;
int dataToBeCombined;
};
// This is the accumulation function we provide to std::accumulate().
// This function will build our results.
class accumulate_op {
public:
Criteria_Result * operator ()(Criteria_Result * x, CustomClass &item);
};
Criteria_Result * accumulate_op::operator ()(Criteria_Result *result, CustomClass &item)
{
if (!result->myCriteria.criteria1.empty() && !item.criteria1.empty()) {
std::map<std::string, int>::iterator it1 = result->result1.find(item.criteria1);
if (it1 == result->result1.end()) // if such of key doesn't exists
{
result->result1.insert(std::make_pair(item.criteria1, item.dataToBeCombined));
}
else
{
// do some other stuff in order to combine data
it1->second += item.dataToBeCombined;
}
result->dataToBeCombined += item.dataToBeCombined;
}
if (!result->myCriteria.criteria2.empty() && !item.criteria2.empty()) {
std::map<std::string, int>::iterator it2 = result->result2.find(item.criteria2);
if (it2 == result->result2.end()) // if such of key doesn't exists
{
result->result2.insert(std::make_pair(item.criteria2, item.dataToBeCombined));
}
else
{
// do some other stuff in order to combine data
it2->second += item.dataToBeCombined;
}
result->dataToBeCombined += item.dataToBeCombined;
}
if (!result->myCriteria.criteria3.empty() && !item.criteria3.empty()) {
std::map<std::string, int>::iterator it3 = result->result3.find(item.criteria3);
if (it3 == result->result3.end()) // if such of key doesn't exists
{
result->result3.insert(std::make_pair(item.criteria3, item.dataToBeCombined));
}
else
{
// do some other stuff in order to combine data
it3->second += item.dataToBeCombined;
}
result->dataToBeCombined += item.dataToBeCombined;
}
return result;
}
int main()
{
Criteria_Result result;
std::vector<CustomClass> aVector;
// set up the criteria for the search
result.myCriteria.criteria1 = "string1";
result.myCriteria.criteria2 = "string2";
for (int i = 0; i < 10; i++) {
CustomClass xx;
xx.dataToBeCombined = i;
if (i % 2) {
xx.criteria1 = "string";
}
else {
xx.criteria1 = "string1";
}
if (i % 3) {
xx.criteria2 = "string";
}
else {
xx.criteria2 = "string2";
}
aVector.push_back (xx);
}
// fold the vector into our results.
std::accumulate (aVector.begin(), aVector.end(), &result, accumulate_op());
std::cout << "Total Data to be combined " << result.dataToBeCombined << std::endl;
std::cout << " result1 list " << std::endl;
for (auto jj : result.result1) {
std::cout << " " << jj.first << " " << jj.second << std::endl;
}
std::cout << " result2 list " << std::endl;
for (auto jj : result.result2) {
std::cout << " " << jj.first << " " << jj.second << std::endl;
}
std::cout << " result3 list " << std::endl;
for (auto jj : result.result3) {
std::cout << " " << jj.first << " " << jj.second << std::endl;
}
std::cout << " Trial two \n\n" << std::endl;
result.myCriteria.criteria2 = "";
result.result1.clear();
result.result2.clear();
result.result3.clear();
result.dataToBeCombined = 0;
// fold the vector into our results.
std::accumulate(aVector.begin(), aVector.end(), &result, accumulate_op());
std::cout << "Total Data to be combined " << result.dataToBeCombined << std::endl;
std::cout << " result1 list " << std::endl;
for (auto jj : result.result1) {
std::cout << " " << jj.first << " " << jj.second << std::endl;
}
std::cout << " result2 list " << std::endl;
for (auto jj : result.result2) {
std::cout << " " << jj.first << " " << jj.second << std::endl;
}
std::cout << " result3 list " << std::endl;
for (auto jj : result.result3) {
std::cout << " " << jj.first << " " << jj.second << std::endl;
}
return 0;
}
Это производит вывод следующим образом:
Total Data to be combined 90
result1 list
string 25
string1 20
result2 list
string 27
string2 18
result3 list
Trial twoTotal Data to be combined 45
result1 list
string 25
string1 20
result2 list
result3 list