Страница 1 из 2

Проблема с добавлением объекта в множество(set)

Добавлено: 12 ноя 2013, 22:01
Titarenko_Vlad
У меня есть функция, которая должна добавить объект в множество которое передается как параметр
Была такая, но объект не добавляла:

Код: Выделить всё

template <class T> void AutoShop::add( set<T> _setSomething)
{
T obg;
__setSomething.insert(obg);
}
Я решил что если объект создается в функции, то он разрушается после выхода из нее. Поэтому я создавал его выше и передавал в функцию как параметр (как ссылку, указатель) все равно не добавляет!
вот код:

Код: Выделить всё

template <class T> void add( set<T> _setSomething, T* _obg)
    {
        _setSomething.insert(*_obg);
    }
вот где она вызывается:

Код: Выделить всё

switch (mapAction[act])
        {
        case manager:
        {
            Manager m;
            au1.add<Manager>(au1.getManagerSet(), &m);
            break;
        }
У меня в классе 3 таких множества и эта ф-я должна делать тоже самое для каждого из них... Можно ил это сделать через шаблонную ф-ю или лучше написать для каждого класса по функции? если не через шаблонную то все работает даже когда объект создается внутри функции:

Код: Выделить всё

void AutoShop::addManager()
{
    Manager manager;
    managerSet.insert(manager);
}

Re: Проблема с добавлением объекта в множество(set)

Добавлено: 13 ноя 2013, 17:04
Romeo
Ошибка сокрыта в незнании азов. Очень рекомендую почитать Страуструпа.

Сэт передаётся в функцию по значенинию, а не по ссылке. Как следствие, на стеке создаётся копия сэта, затем в неё добавляется новый объект, затем копия сета умирает. Внешний сэт при этом остаётся неизменным.

В последнем примере нет не только темплейтов, но и передачи сэта через параметр. Работает всё именно по второй причине, а не по первой.

Re: Проблема с добавлением объекта в множество(set)

Добавлено: 13 ноя 2013, 18:15
Titarenko_Vlad
Да, спасибо. Вот как я это разрешил
поле класса которое сет я сделал статик и возвращал его по указателю

Код: Выделить всё

class AutoShop
{
	string name;
	string address;
	int amountCarsSold;
	double gainsMoney;
	static set<Manager> managerSet;
	static set<Client> clientSet;
	static set<AutoConfiguration> autoConfigurationSet;
public:
	AutoShop(string _name, string _address);
	template <class T> void add( set<T>& _setSomething)
	{
		T obg;
		(_setSomething).insert(obg);
	}
	set<Manager>& getManagerSet() const;
	set<Client>& getClientSet() const;
	set<AutoConfiguration>& getAutoConfigurationSet() const;
/////////////////////
set<Manager>& AutoShop::getManagerSet() const
{
	return managerSet;
}
////////////////////
case manager:
			autoShop.add<Manager>(autoShop.getManagerSet());
			break;
//////////////////
Все работает как надо, но у меня остался 1 вопрос, а можно ли вернуть сет из класса не делая его статиком?

Re: Проблема с добавлением объекта в множество(set)

Добавлено: 13 ноя 2013, 20:41
Romeo
Ну начнём с того, что добавив static в объявление сэта мы не просто "заствили программу компилироваться". Мы напрочь поменяли смысл этого сэта. Как только мы сделали сэт статическим, он стал общим для всех инстансов класса AutoPost. Я очень сомневаюсь, что мы хотели этого добиться.

Далее, как бороться с ошибкой компиляции, которая выскакивает, если мы убираем ключевое слово static из объявления любого из сэтов. Дело в том, что мы задекларировали метод-геттер (для примера возьмём getManagerSet), как константный (const в конце). Что это обозначает? Это обозначает, что метод не имеет право менять поля своего объекта. Если рассмотреть чуть глубже, как подобный запрет реализован на уровне типов языка, то мы поймём, что в константном методе this имеет тип const AutoPost* const (константный указатель на константные данные), в то время как в обычном методе он имеет тип AutoPost* const (константный указатель на неконстантные данные). Из этого следует, что в константном методе все поля класса, так же являются константными (так как доступ к ним производится, явно или неявно, через this, а последний константен). Таким образом, становится понятно почему компилятор ругается - мы пытаемся константный сэт вернуть по неконстантной ссылке наружу, где его смогут поменять, что нарушит правило константного метода.

На самом деле, это всё - лишь вершина айсберга. Если мы изменим сигнатуру геттера на такую:

Код: Выделить всё

const set<Manager>& AutoShop::getManagerSet() const
То у нас скомпилируется в этом месте, но перестанет компилироваться в другом. Квалификатор const придётся добавить ещё и в параметр метода add. А после очередного запуска компиляции мы наконец-то увидим ту ошибку, которую побороть уже не сможем. А именно, компилятор скажет, что не может вызывать неконстантный метод insert у константного сэта.

К чему я это всё клоню? К тому, что идея делать геттер getManagerSet константным была в корне не верна с самого начала. Константный метод предусматривает запрет на модификацию состояния объекта, при чём не только явную, но и неявную (через возврат ссылок или указателей и последующей модификации по ним). Мы же наш геттер используем как раз для того, чтобы менять сэт. Это как раз наша цель! Таким образом мы пришли к противоречию.

Предлагаю полностью пересмотреть дизайн класса. Первое, что мы можем сделать - это убрать const из объвления геттеров для сэтов. И это обосновано. Мы хотим менять эти сэты. Далее возникает вопрос, а зачем нам вообще нужны геттеры и зачем нам вообще нужен template метод add? Ведь изначальный вариант, когда было три функции addManager, addClient и addAutoConfiguration не только содержал меньше кода в целом, но и был более правильным с точки зрения инкапсуляции (у нас не было трёх геттеров, которые может дёргать кто угодно извне и менять наши сэты, даже если мы этого не хотим). С другой стороны, если template метод - это обязательно требование (предположим такое требование было сделано начальником или преподавателем - не суть важно кем именно), то в любом случае есть более изящное решение, а именно сделать специализацию template метода add для трёх этих типов. В таком случае от геттеров, нарушающих инкапсуляцию, также можно будет избавиться.

Re: Проблема с добавлением объекта в множество(set)

Добавлено: 13 ноя 2013, 22:00
Titarenko_Vlad
Спасибо)
Про статик Вы правы, не совсем то что нужно, я осознавал что этот сэт будет общий для всех, но мне нужен только 1 объект этого класса так что все работало как нужно было.
Про конст в конце метода очень познавательно, спасибо. Я все время считал, что это значит что метод не может менять значений класса. Я запомню.
У меня нет специальных условий использовать шаблоны, просто хотелось написать большую программу использующую какие-то мощные или крутые возможности языка, что бы в этом всем разобраться и понять какие-то азы и подводные камни( что в общем то и происходит :) ).
А вот про шаблоны для каждого метода addManager, addClient, addAutoConfiguration я не понял... Сделать эти 3 метода шаблонными? Зачем?

Re: Проблема с добавлением объекта в множество(set)

Добавлено: 13 ноя 2013, 22:07
Titarenko_Vlad
У меня там потом будет еще один шаблонный метод для этих сэтов, так что от этих геттеров польза все же есть, кода будет меньше... Это влияет пользу их использования?)

Re: Проблема с добавлением объекта в множество(set)

Добавлено: 14 ноя 2013, 14:42
Romeo
Если будет ещё один метод, который будет использовать геттеры для сэта, то геттеры можно оставить. Правда я бы посоветовал следующие изменения:

1. Сделать один темлейтный геттер getSet и написать для него три специализации.
2. Спрятать этот гетер в private секцию класса, чтобы никто его не смог вызвать снаружи и "попортить" наши сеты.
3. Убрать параметр из метода add (мы ведь спрятали геттер, так что снаружи мы не сможем его вызвать, чтобы потом передать сэт по параметру), а вместо этого сделать вызов геттера внутри метода add.

В кратце, что такое специализация шаблона. Это возможность написать специальное, кардинально отличающееся решение для конкретного типа шаблонной функции/класса. При этом компилятор будет сам решать в месте вызова какой именно инстанс шаблона использовать - сгенерированый по общей схеме или специализированный. Решение он будет применять, конечно же, на основании типов. Если по типу он определить сам не сможет, то он выдаст ошибку, и ему нужно будет помочь, указав правальный тип в угольных скобочках после имени функции/класса.

Вот простой пример:

Код: Выделить всё

template <class Type>
int compare(const Type left, const Type right)
{
   int result = 0;
   if (left < right) result = -1;
   else if (left > right) result = 1;
   return result;
}

template <>
int compare<char*>(const char* left, const char* right)
{
   return strcmp(left, right);
}

void main()
{
   // common call
   std::cout << compare((int)1, (int)2);
   // common call
   std::cout << compare((float)1.1, (float)2.2);
   // call of specialization
   std::cout << compare("String1", "String2");
}

Re: Проблема с добавлением объекта в множество(set)

Добавлено: 15 ноя 2013, 02:31
Titarenko_Vlad
Спасибо! Крутая штука, раньше я такого не встречал...
Я понял что Вы мне предлагаете и вот что я сделал

Код: Выделить всё

class AutoShop
{
	string name;
	string address;
	int amountCarsSold;
	double gainsMoney;
	set<Manager> managerSet;
	set<Client> clientSet;
	set<AutoConfiguration> autoConfigurationSet;
	template <class T> set<T>& getSet()
	{}
	template<> set<Manager>& getSet<Manager>()
	{
		return managerSet;
	}
	template<> set<Client>& getSet<Client>()
	{
		return clientSet;
	}
	template<> set<AutoConfiguration>& getSet<AutoConfiguration>()
	{
		return autoConfigurationSet;
	}
public:
	AutoShop(string _name, string _address);
	template <class T> void add()
	{
		T obg;
		getSet<T>().insert(obg);
	}
вызываю теперь без параметров, это понятно

Код: Выделить всё

case manager:
			autoShop.add<Manager>();
			break;
Код компилится, но в результате я получаю то, что у меня было в начале, объект не добавляется к множеству. Что я сделал не так?

У меня есть ф-я которая использует те же гетеры, и она работает

Код: Выделить всё

template <class T> void show()
	{
		set<T>::iterator iter;
		iter=getSet<T>().begin();
		while(iter!=getSet<T>().end())
		{
			(*iter).show();
			iter++;
		}
	}
Значит геттеры возвращают нужные сэты, почему тогда объект не добавляется?

Re: Проблема с добавлением объекта в множество(set)

Добавлено: 15 ноя 2013, 20:36
Romeo
В тестовых целях переделал set на vector, так как для того, чтобы объявить сет структур нужно дополнительно определить оператор < для этой структуры, а это бы заняло больше времени. Как следствие insert поменялся на push_back. Также обрати внимание, как я поступил с методом getSet. Пустой шаблонный метод я лишил тела, превратив эту строку в объявление. Специализации вынес за пределы класса. То, что шаблон лишили тела - это стандартный подход в таких случаях. После этого метод можно будет использовать только с типами, на которые написана специализация. Если вызывать метод с другим типом - это даст ошибку компиляции. Таким образом мы защитимся от ошибки вызова, ведь мы не хотим, чтобы метод вызывался для других типов.

Код: Выделить всё

#include <vector>
#include <string>
#include <iostream>

#define set vector

using namespace std;

struct Manager
{
};

struct Client
{
};

struct AutoConfiguration
{
};

class AutoShop
{
     string name;
     string address;
     int amountCarsSold;
     double gainsMoney;
     set<Manager> managerSet;
     set<Client> clientSet;
     set<AutoConfiguration> autoConfigurationSet;
     template <class T> set<T>& getSet();

public:
     AutoShop(string _name, string _address);
     template <class T> void add();
     void show();
};

template<> set<Manager>& AutoShop::getSet<Manager>()
{
    return managerSet;
}

template<> set<Client>& AutoShop::getSet<Client>()
{
    return clientSet;
}

template<> set<AutoConfiguration>& AutoShop::getSet<AutoConfiguration>()
{
    return autoConfigurationSet;
}

AutoShop::AutoShop(string _name, string _address) :
   name(_name),
   address(_address),
   amountCarsSold(),
   gainsMoney(),
   managerSet(),
   clientSet(),
   autoConfigurationSet()
{

}

template <class T> void AutoShop::add()
{
    //getSet<T>().insert(T());
    getSet<T>().push_back(T());
}

void AutoShop::show()
{
   cout <<
      "managerSet.size() = " << managerSet.size() << endl <<
      "clientSet.size() = "  << clientSet.size() << endl <<
      "autoConfigurationSet.size() = "  << autoConfigurationSet.size() << endl;
}

int main()
{
   AutoShop aShop("name", "address");

   aShop.add<Manager>();
   aShop.add<Client>();
   aShop.add<AutoConfiguration>();

   aShop.show();

   return 0;
}
После запуска програмы, я вижу следующий вывод в консоль:
managerSet.size() = 1
clientSet.size() = 1
autoConfigurationSet.size() = 1
Как видишь, всё работает. Так что ошибка где-то в том коде, который ты не привёл на форуме, и который я дописывал сам, чтобы можно было скомпилировать пример. Сравнивай :)

Re: Проблема с добавлением объекта в множество(set)

Добавлено: 15 ноя 2013, 23:41
Titarenko_Vlad
О, спасибо!)
Все заработало когда в ф-цию add сделал как у Вас:

Код: Выделить всё

template <class T> void add()
	{
		getSet<T>().insert(T());
	}
Я только не понял, почему нельзя было объявить объект выше и передать его в insert как было раньше, в нешаблонных ф-циях это работало.
И еще не получилось вынести частные геттеры за класс, выдает такую ошибку на каждый сэт:
error LNK2005: "private: class std::set<class AutoConfiguration,struct std::less<class AutoConfiguration>,class std::allocator<class AutoConfiguration> > & __thiscall AutoShop::getSet<class AutoConfiguration>(void)" (??$getSet@VAutoConfiguration@@@AutoShop@@AAEAAV?$set@VAutoConfiguration@@U?$less@VAutoConfiguration@@@std@@V?$allocator@VAutoConfiguration@@@3@@std@@XZ) already defined in autoShop.obj
Оставил так:

Код: Выделить всё

class AutoShop
{
	string name;
	string address;
	int amountCarsSold;
	double gainsMoney;
	set<Manager> managerSet;
	set<Client> clientSet;
	set<AutoConfiguration> autoConfigurationSet;
	template <class T> set<T>& getSet();
	template<> set<Manager>& getSet<Manager>()
	{
		return managerSet;
	}
	template<> set<Client>& getSet<Client>()
	{
		return clientSet;
	}
	template<> set<AutoConfiguration>& getSet<AutoConfiguration>()
	{
		return autoConfigurationSet;
	}
public:
	AutoShop(string _name, string _address);
	template <class T> void add()
	{
		getSet<T>().insert(T());
	}
	void showAll();
	template <class T> void show()
	{
		set<T>::iterator iter;
		iter=getSet<T>().begin();
		while(iter!=getSet<T>().end())
		{
			(*iter).show();
			iter++;
		}
	}
#pragma once везде прописано... В чем ошибка?