Учитывая следующий шаблон:
template <typename T>
class wrapper : public T {};
Какие видимые различия в интерфейсе или поведении существуют между объектом типа Foo
и объектом типа wrapper<Foo>
?
IМне уже известно об одном:
wrapper<Foo>
имеет только нулевой конструктор, конструктор копирования и оператор присваивания (и он есть только в том случае, если эти операции действительны на Foo
).Эта разница может быть смягчена наличием набора шаблонных конструкторов в wrapper<T>
, которые передают значения конструктору T.
Но я не уверен, какие могут быть другие обнаружимые различия, или еслиесть способы их скрыть.
(Правка) Конкретный пример
Некоторые люди, кажется, просят некоторый контекст для этого вопроса, поэтому вот (несколько упрощенное) объяснение моегоситуация.
Я часто пишу код, значения которого можно настроить для точной настройки производительности и работы системы.Я хотел бы иметь простой (низкое количество кода) способ предоставления таких значений через файл конфигурации или пользовательский интерфейс.В настоящее время я пишу библиотеку, чтобы позволить мне сделать это.Предполагаемый дизайн позволяет использовать что-то вроде этого:
class ComplexDataProcessor {
hotvar<int> epochs;
hotvar<double> learning_rate;
public:
ComplexDataProcessor():
epochs("Epochs", 50),
learning_rate("LearningRate", 0.01)
{}
void process_some_data(const Data& data) {
int n = *epochs;
double alpha = *learning_rate;
for (int i = 0; i < n; ++i) {
// learn some things from the data, with learning rate alpha
}
}
};
void two_learners(const DataSource& source) {
hotobject<ComplexDataProcessor> a("FastLearner");
hotobject<ComplexDataProcessor> b("SlowLearner");
while (source.has_data()) {
a.process_some_data(source.row());
b.process_some_data(source.row());
source.next_row();
}
}
При запуске это установит или прочитает следующие значения конфигурации:
FastLearner.Epochs
FastLearner.LearningRate
SlowLearner.Epochs
SlowLearner.LearningRate
Это составленный код (как это происходитмой вариант использования - это даже не машинное обучение), но он показывает пару важных аспектов дизайна.Все настраиваемые значения имеют имена и могут быть организованы в иерархию.Значения могут быть сгруппированы несколькими методами, но в приведенном выше примере я просто покажу один метод: оборачивание объекта в класс hotobject<T>
.На практике оболочка hotobject<T>
выполняет довольно простую задачу - она должна поместить имя объекта / группы в стек локального контекста потока, а затем разрешить создание объекта T
(после чего hotvar<T>
значения создаются и проверяют стек контекста, чтобы увидеть, в какую группу они должны входить), затем извлекаем стек контекста.
Это делается следующим образом:
struct hotobject_stack_helper {
hotobject_stack_helper(const char* name) {
// push onto the thread-local context stack
}
};
template <typename T>
struct hotobject : private hotobject_stack_helper, public T {
hotobject(const char* name):
hotobject_stack_helper(name) {
// pop from the context stack
}
};
Насколько я могускажем, порядок построения в этом сценарии довольно четко определен:
hotobject_stack_helper
создается (помещая имя в стек контекста) T
создается - включаяконструирование каждого из T
членов (горячих переменных) - Тело конструктора
hotobject<T>
запускается, что вызывает контекстный стек.
Итак, у меня есть работакод для этого.Однако остается вопрос: какие проблемы я мог бы вызвать для себя в дальнейшем, используя эту структуру.Этот вопрос в значительной степени сводится к вопросу, который я на самом деле задаю: как hobobject будет вести себя иначе, чем сам T?