Общие методы программирования шаблонов: ограничивать типы или не ограничивать типы - PullRequest
13 голосов
/ 18 мая 2011

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

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

Кто-нибудь может иметь какие-либо источники для статистики или авторитетных комментариев по этой теме? Мне также интересны здравые мнения. Надеюсь, это не лишит законной силы этот вопрос в целом: \

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

Редактировать: В одном ответе упоминалось, что тип библиотеки, которую я делаю, будет значительным. Это библиотека базы данных, которая в итоге работает с контейнерами STL, variadics (tuple), Boost Fusion и тому подобными вещами. Я понимаю, насколько это уместно, но мне также были бы интересны практические правила для определения того, каким путем идти.

Ответы [ 5 ]

14 голосов
/ 18 мая 2011

Всегда оставляйте его как можно более открытым - но обязательно

  • документирует требуемый интерфейс и поведение допустимых типов для использования с вашим универсальным кодом.
  • используйте характеристики интерфейса типа (черты), чтобы определить, разрешить или запретить его. Не основывайте свое решение на имени типа.
  • поставить обоснованный диагноз если кто-то использует неправильный тип. C ++ шаблоны хороши для поднятия тонн глубоко вложенных ошибок, если они создаются с неправильные типы - используя черты типа, статические утверждения и связанные методы, можно легко создавать более краткие сообщения об ошибках.
3 голосов
/ 18 мая 2011

В своей структуре базы данных я решил отказаться от шаблонов и использовать один базовый класс.Общее программирование означало, что могут использоваться любые или все объекты.Классы определенных типов перевесили несколько общих операций.Например, строки и числа можно сравнить на равенство;BLOB (большие двоичные объекты) могут использовать другой метод (например, сравнивать контрольные суммы MD5, хранящиеся в другой записи).

Также существовала ветка наследования между строками и числовыми типами.

Используя иерархию наследования, я могу ссылаться на любое поле, используя класс Field или специализированный класс, такой как Field_Int.

3 голосов
/ 18 мая 2011

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

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

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

2 голосов
/ 18 мая 2011

Проблема

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

Например:

namespace Database
{

// internal API, not documented
template <class DatabaseItem>
void
store(DatabaseItem);
{
    // ...
}

struct SomeDataBaseType {};

}  // Database

namespace ClientCode
{

template <class T, class U>
struct base
{
};

// external API, documented
template <class T, class U>
void
store(base<T, U>)
{
    // ...
}

template <class T, class U>
struct derived
    : public base<T, U>
{
};

}  // ClientCode

int main()
{
    ClientCode::derived<int, Database::SomeDataBaseType> d;
    store(d);  // intended ClientCode::store
}

В этом примере автор main даже не знает, что Database :: store существует. Он намеревается вызвать ClientCode :: store и становится ленивым, позволяя ADL выбирать функцию вместо указания ClientCode::store. В конце концов, его аргумент store исходит из того же пространства имен, что и store, поэтому он должен просто работать.

Это не работает. Этот пример вызывает Database::store. В зависимости от внутренних параметров Database::store этот вызов может привести к ошибке времени компиляции или, что еще хуже, ошибке времени выполнения.

Как исправить

Чем более обобщенно вы называете свои функции, тем более вероятно, что это произойдет. Дайте вашим внутренним функциям (те, которые должны появляться в ваших заголовках) действительно неуниверсальные имена. Или поместите их в подпространство имен, например details. В последнем случае вы должны убедиться, что ваши клиенты никогда не будут иметь details в качестве ассоциированного пространства имен для целей ADL. Обычно это достигается путем не создания типов, которые клиент будет использовать, прямо или косвенно, в namespace details.

Если вы хотите стать более параноиком, начните блокировать вещи с enable_if.

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

Приведенный выше пример кода не является надуманным. Это случилось со мной. Это случилось с функциями в namespace std. Я называю store в этом примере чрезмерно общим . std::advance и std::distance являются классическими примерами чрезмерно общего кода. Это то, что нужно остерегаться. И это проблема понятий, которые пытались исправить.

2 голосов
/ 18 мая 2011

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

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

...