Мне нужны классы Derived1
и Derived2
, производные от абстрактного базового класса Base
.
У меня также есть класс Container
, который содержит карту уникальных указателей на объекты класса Base
, потому что я хочу реализовать полиморфизм во время выполнения.
В классе Container
есть функция add
, которая принимает уникальный указатель на объект базового класса и добавляет его в map
. Я заметил, что часто знаю конкретный тип добавляемого объекта, поэтому добавил перегруженную шаблонную функцию добавления, которая принимает произвольный объект по значению, перемещает его в уникальный указатель и вызывает другую функцию add
.
Вот минимальный пример:
#include <map>
#include <memory>
#include <string>
class Base {
public:
virtual ~Base();
};
class Derived1 : Base {
};
class Derived2 : Base {
};
class Container {
public:
std::map<std::string, std::unique_ptr<Base>> coll;
void add(std::string name, std::unique_ptr<Base> elem) {
coll.emplace(name, std::move(elem));
}
template<typename T>
void add(std::string name, T elem) {
this->add(name, std::make_unique<T>(std::move(elem)));
}
};
int main() {
Container c;
//a little more convenient than c.add("1", std::make_unique<Derived1>());
c.add("1", Derived1());
}
При попытке скомпилировать это с помощью clang 16, кажется, что он зависает и медленно использует все больше и больше оперативной памяти, пока мои 32 ГБ не заполнятся. gcc, похоже, тоже завис, но не использует все больше и больше оперативной памяти.
Я могу это исправить, просто переименовав одну из функций add
, но мне все равно интересно, что здесь происходит.
Это ожидаемое поведение, потому что я нарушил какое-то зловещее правило C++, которое вам просто необходимо знать? Я думаю, что зависание компилятора — это то, чего никогда не должно происходить, независимо от ввода.
Таким образом, либо я сделал что-то, что не встречается в других базах кода, поэтому эта ошибка компилятора никогда не была замечена, либо я сделал что-то настолько необычное, что никто никогда не удосужился найти более чистое решение, чем зависание компилятора. Это будет мой второй вопрос: есть ли в моем подходе что-то настолько унидиоматичное, что это не кажется проблемой другим?
Этот шаблон метода:
template<typename T>
void add(std::string name, T elem) {
this->add(name, std::make_unique<T>(std::move(elem)));
}
Вызывает бесконечный цикл создания экземпляров методов.
Это называется инициализацией с T
=Derived1
,
тогда с T
=std::uqniue_ptr<Derived1>
,
тогда с T
=std::uqniue_ptr<std::unique_ptr<Derived1>>
,
и так далее.
Компилятор продолжает создавать экземпляры этих различных методов до тех пор, пока в конечном итоге у него не закончится память или он просто не застрянет в бесконечном цикле, пытаясь продолжить создание экземпляров.
Проблема возникает и в MSVC (в MSVC я в итоге получил: fatal error C1060: compiler is out of heap space
).
Если вы переименуете, например. имя нешаблонного метода и вызовите его из шаблонного (как вы заметили), вы разорвете этот цикл и, следовательно, проблема не возникнет.
Это имеет смысл, спасибо, что уделили время!
Альтернатива:
template<typename T, typename...Ts> void add(std::string name, Ts&&... args) { this->add(name, std::make_unique<T>(std::forward<Ts>(args)...)); }
с использованиемc.add<Derived>("name"/*, constructor args */)