Неправильное использование шаблонов? - PullRequest
6 голосов
/ 16 августа 2011

Я понимаю, что шаблоны как бы обвиняют в двоичном раздутии, я также понимаю, что шаблон - это просто шаблон.Я действительно не понимаю гайки и болты как где.

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

class GameObject
{
public:
    Component* getComponent(std::string key);
};

static_cast<Comp>(obj.getComponent("Comp"));

Вместо того, чтобы делать метод шаблоном.

class GameObject
{
public:
    template<typename T>
    T* getComponent(std::string key);
};

obj.getComponent<Comp>("Comp");

Это стилистика или потеря шаблонов связана с шаблонами?

Ответы [ 6 ]

3 голосов
/ 16 августа 2011

Тот факт, что метод принимает «ключ», который, по-видимому, является действительно типом (тип возвращаемого компонента), указывает мне, что тип на самом деле не известен до времени выполнения.Если это так, то механизм времени компиляции, такой как шаблоны, не подойдет.

Единственный вариант - вернуть указатель на базовый класс.И, как правило, когда используется этот шаблон, против базового класса вызываются только виртуальные методы - поэтому фактический тип производного класса не имеет значения (поэтому не требуется static_cast или dynamic_cast).

Edit:

Как отметил PhilCK в комментариях, тип фактически известен во время компиляции.Если это так, то поиск динамического типа в действительности никогда не требовался, и можно было бы использовать простые фабричные методы:

class GameObject {
    A getComponentA();
    B getComponentB();
    C getComponentC();
    // etc.
}

// which is more or less identical to:

class ComponentFactory {
    public:
    virtual Component* create() = 0;
};
class GameObject {
    std::map<std::string,ComponentFactory> m;
    public:
    GameObject() {
        // presumably a private map has to be populated with string -> factory methods here
    }

    template<class T>
    T* getComponent(const std::string& s) { 
        // either the static_cast is needed here, or an alternate (messier)
        // implementation of getComponent is needed.
        // it's still possible for the wrong type to be passed (by mistake)
        // and the compiler won't catch it (with static_cast). Since this could lead
        // to heap corruption the only safe way with this type of 
        // implementation is dynamic_cast.
        return static_cast<T>(m[s].create());
    }
};

// this doesn't even compile:
// return types:
class GameObject {
    template <class T>
    T* getComponent(const std::string& s) {
        if (s == "A") return new A();
        else if (s == "B") return new B();
        // etc..
        else throw runtime_error("bad type");
    }
}

Таким образом, на мой взгляд, есть 2 варианта.

1) использовать простые фабричные методы, в которых шаблоны вообще не нужны.2) использовать реализацию метода map -> factory вместе с dynamic_cast (которая, кажется, лишает цели использования создания динамических типов) и на самом деле излишне сложна, если динамические типы не нужны

1 голос
/ 16 августа 2011

В нулевом порядке не должно быть разницы в производительности.

Шаблонный метод создаст функцию-член для каждого T. Это само по себе не делает этот код более медленным, но может сделать вызов функции более дорогим из-за проблем с локальностью кода, поскольку приведение, вероятно, выполняется дальше от сайта вызова. Только профилирование может сказать вам.

Возможно, есть и другие узкие места, о которых нужно беспокоиться, например, передача аргументов по значению, а не по константной ссылке.

Component* getComponent(std::string key);
Component* getComponent(const std::string& key);

Кроме того, эта функция, вероятно, может быть const.

0 голосов
/ 16 августа 2011

Похоже, ваше использование так или иначе связано с кастом.В этом случае я бы выполнил приведение в методе template, который находится под вашим контролем.

Основное различие между этими двумя подходами состоит в том, что первый фрагмент не проверяет, является ли допустимым выполнение приведения (иесли вы не используете dynamic_cast, это невозможно проверить).

Во втором фрагменте класс может бросить или вернуть указатель NULL, если тип для приведения не является типом объекта по этому идентификатору.

Я бы предположилраздувание шаблона, если таковое имеется, будет минимальным, так как вы можете в любом случае делегировать реальную работу помощнику, не являющемуся шаблоном, и единственное, что шаблон будет делать в дополнение, это выполнить static_cast.Когда он встроен, он будет таким же, как и не шаблонная версия (за исключением того, что у вас есть возможность сделать функцию умнее, проверив тип результата).

0 голосов
/ 16 августа 2011

Раздувание может быть проблемой с шаблонами, а раздувание само по себе может вызвать проблемы с производительностью. Тем не менее, хороший компилятор может оптимизировать некоторые проблемы с раздуванием, и есть методы программирования, которые (при необходимости) могут избежать остальных.

Это не должно быть проблемой здесь - ваша шаблонная функция, вероятно, будет тривиальной, и, следовательно, встроенной. Это может в принципе вызвать некоторое вздутие само по себе, но поскольку функция тривиальна, количество «раздувания» тоже тривиально.

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

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

0 голосов
/ 16 августа 2011

Компилятор преобразует код шаблона в сборку.Если это будет сделано для многих типов, это приведет к многократному повторению блоков кода сборки.Этого можно избежать, не используя шаблоны.Или часто использование шаблонов является функциями-обертками для не шаблонных функций или небольшим количеством шаблонных функций.Потеря производительности будет связана с большими файлами.С другой стороны, общая функция может быть более сложной из-за использования указателей / ссылок вместо объектов.И у компилятора меньше возможностей встроить их.

Другая причина раздувания - это то, что функция шаблона создается в каждой dll, которую они используют.

0 голосов
/ 16 августа 2011

Если я правильно понимаю ваш пример, вопрос не в шаблонах, раздувающих двоичные файлы, а в том, использовать ли шаблоны или полиморфизм.

Короче говоря, шаблоны допускают гибкие типы во время компиляции, в то время как полиморфизм допускает гибкие типы ввремя выполнения, используя наследование.Конечно, есть еще много чего.Какой из них использовать, зависит от ваших реальных потребностей.В вашем случае я бы сказал, что это зависит от отношения между различными типами компонентов.Если они разделяют функциональность, у них должен быть общий базовый класс, здесь Component.Если у них нет ничего общего, вы должны использовать шаблоны.

Производительность WRT, если вы не в реальном времени, что обычно не будет основной причиной выбора между ними.Шаблоны могут генерировать даже меньше двоичных файлов, чем обычные классы, потому что создается только то, что вы используете, и накладные расходы на виртуальные функции (используемые в наследовании) невелики.Так что это обычно вопрос стиля (или лучшей практики).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...