Я пытаюсь создать структуру проекта, отвечающую следующим целям:
- приложению необходимо создать экземпляры классов с помощью фабрики;
- классы, экземпляры которых созданы фабрикой, должны иметь возможность самостоятельно регистрироваться в фабрике;
- Использование системы саморегистрации должно быть очень простым и надежным.
Эта часть проблемы прекрасно решается в сообщении в блоге Нира Фридмана по теме (хотя я получаю предупреждения о компиляции, когда использую CLang, но это уже другая история).
Теперь все становится сложнее, когда я пытаюсь применить это в контексте проекта, в котором люди хотели бы добавлять классы, просто добавляя источники. Основываясь на примере Нира (из которого я удалил несколько вещей для удобства), я создал следующий макет (на основе сообщение Рафаэля Вараго) [см. репозиторий GitHub]:
.
├── CMakeLists.txt
├── app
│ ├── CMakeLists.txt
│ └── src
│ └── main.cpp
└── libs
├── CMakeLists.txt
├── libanimal
│ ├── CMakeLists.txt
│ ├── include
│ │ └── animal
│ │ └── Animal.h
│ └── src
│ ├── Cat.cpp
│ └── Dog.cpp
└── libfactory
├── CMakeLists.txt
└── include
└── factory
└── Factory.h
Когда я писал CMakeLists.txt
файлы, я пытался применить современные методы CMake, за одним исключением, которое будет подробно описано ниже.
Каталог app
содержит код приложения, вызывающего фабрику:
#include <animal/Animal.h>
int main() {
auto x = Animal::make("Dog", 3);
auto y = Animal::make("Cat", 2);
x->makeNoise();
y->makeNoise();
return 0;
}
Каталог libs
содержит два подкаталога:
libfactory
содержит код шаблона фабрики и построен как каталог только для заголовков;libanimal
содержит абстрактный классAnimal
и связанную с ним фабрику, а также код для дочерних классов; он построен как статическая библиотека с зависимостью отlibfactory
.
Я хочу, чтобы libanimal
имел своего рода поведение «библиотеки плагинов времени компиляции»: потомки класса Animal
будут самостоятельно регистрироваться на Animal
factory после компиляции. Этой цели должным образом служит (по крайней мере, на бумаге) метод Нира (Animal.h
):
#pragma once
#include <factory/Factory.h>
struct Animal : Factory<Animal, int> {
Animal(Key) {}
virtual void makeNoise() = 0;
};
Теперь я хочу объединить это с возможностью централизовать дочерний код в одном файле cpp, который CMake автоматически обнаруживает при сборке проекта. Преимущество этого заключается в том, что он позволяет очень легко добавлять и удалять функции (просто вставьте новый файл или удалите его). Для этой цели я использовал глобус в libanimal
CMakeLists.txt
, тем самым нарушая современные передовые практики CMake. Если есть лучший способ добиться этого, я, конечно, буду рад его реализовать. Код для Dog.cpp
:
#include <iostream>
#include <animal/Animal.h>
class Dog : public Animal::Registrar<Dog> {
public:
Dog(int x) : m_x(x) {}
void makeNoise() override { std::cerr << "Dog: " << m_x << "\n"; }
private:
int m_x;
};
Когда я создаю проект, кажется, что все идет нормально, за исключением предупреждений, которые я получаю также при компиляции проекта Nir (я получаю их с помощью clang, но не с помощью gcc):
In file included from /Users/vincent/Documents/src/personal/sandboxes/cpp_factory_split/app/src/main.cpp:3:
In file included from /Users/vincent/Documents/src/personal/sandboxes/cpp_factory_split/libs/libanimal/include/animal/Animal.h:4:
In file included from /Users/vincent/Documents/src/personal/sandboxes/cpp_factory_split/libs/libfactory/include/factory/Factory.h:4:
In file included from /Library/Developer/CommandLineTools/usr/bin/../include/c++/v1/string:505:
In file included from /Library/Developer/CommandLineTools/usr/bin/../include/c++/v1/string_view:176:
In file included from /Library/Developer/CommandLineTools/usr/bin/../include/c++/v1/__string:57:
In file included from /Library/Developer/CommandLineTools/usr/bin/../include/c++/v1/algorithm:644:
/Library/Developer/CommandLineTools/usr/bin/../include/c++/v1/memory:2339:5: warning: delete called on 'Animal' that is abstract but has non-virtual destructor [-Wdelete-abstract-non-virtual-dtor]
delete __ptr;
^
/Library/Developer/CommandLineTools/usr/bin/../include/c++/v1/memory:2652:7: note: in instantiation of member function 'std::__1::default_delete<Animal>::operator()' requested here
__ptr_.second()(__tmp);
^
/Library/Developer/CommandLineTools/usr/bin/../include/c++/v1/memory:2606:19: note: in instantiation of member function 'std::__1::unique_ptr<Animal, std::__1::default_delete<Animal> >::reset' requested here
~unique_ptr() { reset(); }
^
/Users/vincent/Documents/src/personal/sandboxes/cpp_factory_split/app/src/main.cpp:6:14: note: in instantiation of member function 'std::__1::unique_ptr<Animal, std::__1::default_delete<Animal> >::~unique_ptr' requested here
auto x = Animal::make("Dog", 3);
^
1 warning generated.
Однако когда я запускаю приложение, я получаю следующую ошибку:
- версия clang:
/Users/vincent/Documents/src/personal/sandboxes/cpp_factory_split/build/app/app
libc++abi.dylib: terminating with uncaught exception of type std::out_of_range: unordered_map::at: key not found
- версия gcc:
/Users/vincent/Documents/src/personal/sandboxes/cpp_factory_split/cmake-build-release-gcc/app/app
terminate called after throwing an instance of 'std::out_of_range'
what(): _Map_base::at
Похоже, это означает, что таблица фабрики пуста, и я не понимаю почему.
Вопросов
- Я неправильно понял замысел Нира?
- Если да на 1., знает ли кто-нибудь о дизайне с саморегистрацией, который потребует столь же минимального обслуживания, как этот, и будет подходить для моего варианта использования?
- Если нет на 1., что я делаю не так?
Animal
. Обязательно при определении интерфейса на C ++. Обратите внимание, что этот dtor был добавлен в конец блога Нира. В качестве альтернативы вы можете добавить его в шаблонFactory
. - person Marek R   schedule 13.06.2019