Какие обнаружимые различия существуют между классом и его базовым классом? - PullRequest
3 голосов
/ 25 августа 2010

Учитывая следующий шаблон:

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
    }
};

Насколько я могускажем, порядок построения в этом сценарии довольно четко определен:

  1. hotobject_stack_helper создается (помещая имя в стек контекста)
  2. T создается - включаяконструирование каждого из T членов (горячих переменных)
  3. Тело конструктора hotobject<T> запускается, что вызывает контекстный стек.

Итак, у меня есть работакод для этого.Однако остается вопрос: какие проблемы я мог бы вызвать для себя в дальнейшем, используя эту структуру.Этот вопрос в значительной степени сводится к вопросу, который я на самом деле задаю: как hobobject будет вести себя иначе, чем сам T?

Ответы [ 4 ]

3 голосов
/ 25 августа 2010

Странный вопрос, так как вы должны задавать вопросы о вашем конкретном использовании («что я хочу сделать, и как это поможет мне или навредит мне»), но я думаю, в общем:

wrapper<T> не является T, поэтому:

  • Он не может быть построен как T.(Как вы заметили.)
  • Он не может быть конвертирован как T.
  • Он теряет доступ к рядовым T имеет доступ к.

И я уверен, что есть еще, но первые два покрывают совсем немного.

2 голосов
/ 25 августа 2010

Предположим, у вас есть:

class Base {};
class Derived : Base {};

Теперь вы можете сказать:

Base *basePtr = new Derived;

Однако вы не можете сказать:

wrapper<Base> *basePtr = new wrapper<Derived>();

То есть, хотя их параметры типа могут иметь отношение наследования, два типа, созданные путем специализации шаблона, не имеют отношения наследования.

0 голосов
/ 25 августа 2010

Если ваш унаследованный класс имеет свои собственные переменные-члены (или хотя бы одну), тогда

sizeof(InheritedClass) > sizeof(BaseClass)
0 голосов
/ 25 августа 2010

Ссылка на объект конвертируема (предоставляется доступ) к ссылке на подобъект базового класса.Существует синтаксический сахар для вызова неявных преобразований, позволяющих вам рассматривать объект как экземпляр базы, но это действительно то, что происходит.Не больше, не меньше.

Итак, разницу совсем нетрудно обнаружить.Это (почти) совершенно разные вещи.Разница между отношениями «есть» и «имеет» указывает имя члена.

Что касается сокрытия базового класса, я думаю, что вы случайно ответили на свой вопрос.Используйте частное наследование, указав private (или пропустив public для class), и эти преобразования не произойдут вне самого класса, и никакой другой класс не сможет сказать, что база даже существует.*

...