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

Модераторы: Hawk, Romeo, Absurd, DeeJayC, WinMain

Titarenko_Vlad
Сообщения: 20
Зарегистрирован: 12 ноя 2013, 21:50

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

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

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);
}
Аватара пользователя
Romeo
Сообщения: 3126
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

Ошибка сокрыта в незнании азов. Очень рекомендую почитать Страуструпа.

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

В последнем примере нет не только темплейтов, но и передачи сэта через параметр. Работает всё именно по второй причине, а не по первой.
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Titarenko_Vlad
Сообщения: 20
Зарегистрирован: 12 ноя 2013, 21:50

Да, спасибо. Вот как я это разрешил
поле класса которое сет я сделал статик и возвращал его по указателю

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

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 вопрос, а можно ли вернуть сет из класса не делая его статиком?
Аватара пользователя
Romeo
Сообщения: 3126
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

Ну начнём с того, что добавив 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 для трёх этих типов. В таком случае от геттеров, нарушающих инкапсуляцию, также можно будет избавиться.
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Titarenko_Vlad
Сообщения: 20
Зарегистрирован: 12 ноя 2013, 21:50

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

У меня там потом будет еще один шаблонный метод для этих сэтов, так что от этих геттеров польза все же есть, кода будет меньше... Это влияет пользу их использования?)
Аватара пользователя
Romeo
Сообщения: 3126
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

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

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");
}
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Titarenko_Vlad
Сообщения: 20
Зарегистрирован: 12 ноя 2013, 21:50

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

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

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++;
		}
	}
Значит геттеры возвращают нужные сэты, почему тогда объект не добавляется?
Аватара пользователя
Romeo
Сообщения: 3126
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

В тестовых целях переделал 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
Как видишь, всё работает. Так что ошибка где-то в том коде, который ты не привёл на форуме, и который я дописывал сам, чтобы можно было скомпилировать пример. Сравнивай :)
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Titarenko_Vlad
Сообщения: 20
Зарегистрирован: 12 ноя 2013, 21:50

О, спасибо!)
Все заработало когда в ф-цию 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 везде прописано... В чем ошибка?
Ответить