2011-02-07 2 views
0

У меня есть функция, которая может создавать множество различных шаблонов; простой пример задачи будет:Как избежать неспособности перегружать возвращаемый тип в C++

EncodedMsg<?>* encode(const Msg& msg) 
{ 
    if(msg.qty < 100) 
     return new EncodedMsg<short>(...); 
    else if(msg.qty < 100000) 
     return new EncodedMsg<int>(...); 
    else 
     return new EncodedMsg<long>(...); 
} 

Проблема у меня в том, что:

  1. Решение относительно того, что шаблон на определяется внутри функции
  2. Я не могу перегрузки на тип возвращаемого
  3. Я не хочу, чтобы возвращать тип базового класса EncodedMsg, как я тогда придется расшифровать, какой тип сообщения он позже

Может ли кто-нибудь подумать об этом?

ответ

8

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

Чтобы изменить тип во время выполнения, вам понадобится полиморфизм из классов и наследования.

0

Такой подход не позволит использовать EncodedMessage везде, где вы хотите, чтобы в дальнейшем (потому что я подозреваю, что, например, EncodedMessage<long> и EncodedMessage<short> оба должны быть проходимый в какой-то метод, который работает на EncodedMessages).

Хотя то, о чем вы просите, может быть сделано с помощью трюков, я бы посоветовал вам переосмыслить вашу архитектуру.

Вы можете просто придерживаться EncodedMessage<long> и вводить его в качестве TheOnlyPossibleEncodedMessage, если он может использоваться для всех объемов сообщений.

3

В принципе, если тестовая переменная msg.qty не является постоянной времени компиляции, это будет невозможно. Вы должны понимать, что шаблоны могут использоваться для достижения статического полиморфизма, тогда как ООП (наследование) может использоваться для достижения динамического полиморфизма. Другими словами, шаблоны разрешаются во время компиляции, тогда как вызовы виртуальных функций разрешаются во время выполнения. Вы можете использовать оба одновременно, но вы не можете использовать их с той же целью (т. Е. Они являются парадигмами дополнительного программирования, каждый из которых имеет свои собственные контексты приложения).

Если бы вы могли, однако, получить msg.qty быть константой во время компиляции типа Msg (т.е. Msg::qty, как enum или static const), то вы можете использовать boost::enable_if библиотеку. Как так:

template <class Msg> 
typename boost::enable_if<Msg::qty < 100, EncodedMsg<short> >::type* 
encode(const Msg& msg) { return new EncodedMsd<short>(...); }; 

template <class Msg> 
typename boost::enable_if<((Msg::qty >= 100) && (Msg::qty < 100000)), EncodedMsg<short> >::type* 
encode(const Msg& msg) { return new EncodedMsd<int>(...); }; 

template <class Msg> 
typename boost::enable_if<Msg::qty >= 100000, EncodedMsg<short> >::type* 
encode(const Msg& msg) { return new EncodedMsd<long>(...); }; 

Но в том случае, если вы можете определить, для определенного класса Msg, какой шаблон конкретизации использовать для EncodedMsg, то это намного проще определить его в качестве вложенного ЬурейеГо в классе Msg, и определить свою функцию кодирования следующим образом:

template <class Msg> 
Msg::encoded_type* encode(const Msg& msg) { return new Msg::encoded_type(...); }; 

вы также можете использовать тип признак (например, message_trait или что-то в этом роде), чтобы определить вложенные ЬурейиЙ, если у вас есть классы сообщений, которые не могут быть изменены.

Но, если msg.qty может быть только значением времени выполнения, тогда нет выбора, кроме как использовать динамический полиморфизм (т. Е. Вернуть указатель на базовый класс или интерфейс).Или вы также можете использовать boost::variant. В этом случае, вы можете сделать это:

boost::variant< EncodedMsg<short>, 
       EncodedMsg<int>, 
       EncodedMsg<long> >* encode(const Msg& msg) 
{ 
    typedef boost::variant< EncodedMsg<short>, 
          EncodedMsg<int>, 
          EncodedMsg<long> > result_type; 
    if(msg.qty < 100) 
    return new result_type(EncodedMsg<short>(...)); 
    else if(msg.qty < 100000) 
    return new result_type(EncodedMsg<int>(...)); 
    else 
    return new result_type(EncodedMsg<long>(...)); 
}; 

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

1

1) Вы не можете перегружать возвращаемый тип функции, но вы можете перегружать типы , переданные функции и проходящие по указателю. Тем не менее, я думаю, что это не то, что вы хотите, см 2)

2) В основном приходится использовать другой дизайн подход к проблеме: С помощью виртуального интерфейса в вашем базовом классе ваш метод возвращает класс правой , и один вызывает только соответствующую виртуальную функцию. Вы просто возвращаете базовый класс (EncodedMsgBase), который может быть EncodedMsg_, независимо от того, как он получен из EncodedMsgBase. Вы можете создать шаблон из базового класса, если хотите.

В более сложных сценариях это сводится к проблеме «как создать проблему с фабрикой в ​​C++». Люди обычно обходятся этим путем передачи базового класса, реализующего «виртуальный конструктор idiom» .

Проверьте с чаво ++ для получения дополнительной информации о том, что:

http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.8

держать Также в виду, что при использовании статической типизации можно избежать многих неприятностей :)

Смежные вопросы