Мегаобучалка Главная | О нас | Обратная связь


Храните типы и функции в разных пространствах имен, если только они не предназначены для совместной работы



2016-01-26 383 Обсуждений (0)
Храните типы и функции в разных пространствах имен, если только они не предназначены для совместной работы 0.00 из 5.00 0 оценок




 

 

Резюме

 

Оберегайте ваши типы от непреднамеренного поиска, зависящего от аргументов (argument-dependent lookup — ADL, известный также как поиск Кёнига); однако преднамеренный поиск должен завершаться успешно. Этого можно добиться путем размещения типов в своих собственных пространствах имен (вместе с непосредственно связанными с ними свободными функциями; см. рекомендацию 57). Избегайте помещения типов в те же пространства имен, что и шаблоны функций или операторов).

 

Обсуждение

 

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

Вот реальный пример, который был опубликован в группе новостей:

#include <vector>

 

namespace N {

struct X { };

template<typename T>

int* operator+(T , unsigned) {/* некоторые действия */}

}

 

int main() {

std::vector<N::X> v(5);

v[0];

}

Инструкция v[0]; компилируется в некоторых реализациях стандартной библиотеки, но не во всех. Попробуем кратко пересказать эту длинную историю. Очень тонкая проблема связана с тем, что внутри большинства реализаций vector<T>::operator[] спрятан код наподобие v.begin()+n, и поиск имен для функции operator+ может достичь пространства имен (в нашем случае N) типа, для которого инстанцирован вектор (в нашем случае X). Достигнет ли поиск этого пространства имен или нет — зависит от того, как определен vector<T>::iterator в данной версии реализации стандартной библиотеки. Однако если поиск достигает N, то здесь он находит N::operator+. Наконец, в зависимости от используемых типов, компилятор может просто посчитать, что для vector<T>::iterator оператор N::operator+ имеет лучшее соответствие, чем оператор std::operator+ из реализации стандартной библиотеки (который и должен был быть вызван). (Один из способов избежать такой неприятности в реализации стандартной библиотеки — не использовать код v.begin()+n таким образом, что он вносит непреднамеренную точку настройки: либо надо изменить код так, чтобы тип v.begin() никаким образом не зависел от параметра шаблона, либо вызов operator+ следует переписать с указанием полного квалифицированного имени. См. рекомендацию 65.)

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

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

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

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

Примечание. Да, стандартная библиотека С++ помещает алгоритмы и другие шаблоны функций, таких как copy или distance, в то же пространство имен, что и множество типов, таких как pair или vector. Все они находятся в одном пространстве имен. Это неудачное решение, которое вызывает описанные весьма тонкие и трудно локализуемые проблемы. К счастью, теперь у нас больше опыта и мы знаем, как следует поступать. Не делайте так, как сделано в стандартной библиотеке.

"Оборотная сторона" этого вопроса рассматривается в рекомендации 57.

 

Ссылки

 

[Stroustrup00] §10.3.2, §11.2.4 • [Sutter00] §34 • [Sutter02] §39-40

 

Не используйте using для пространств имен в заголовочных файлах или перед директивой #include

 

 

Резюме

 

Директива using для пространств имен создана для вашего удобства, а не для головной боли других. Никогда не используйте объявления или директивы using перед директивой #include.

Вывод: не используйте директивы using для пространств имен или using-объявления в заголовочных файлах. Вместо этого полностью квалифицируйте все имена. (Второе правило следует из первого, поскольку заголовочные файлы не могут знать, какие другие директивы #include могут появиться в тексте после них.)

 

Обсуждение

 

Вкратце: вы можете и должны свободно и без ограничений использовать объявления и директивы using в своих файлах реализации после директив #include. Несмотря на повторяющиеся заявления их противников, объявления и директивы using не являются злом и не противоречат цели пространств имен. Они просто делают пространства имен более удобными в использовании.

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

Однако директивы и объявления using предназначены только для вашего удобства и вы не должны использовать их так, чтобы они влияли на какой-то другой код. В частности, их нельзя употреблять где попало, где за ними может следовать еще какой-то сторонний код. В частности, их не следует использовать в заголовочных файлах (которые предназначены для включения в неограниченное количество файлов реализации — вы не должны вносить путаницу в значение кода в этих файлах) или перед директивой #include (тем самым вы по сути вносите их в текст этих заголовочных файлов).

Большинство программистов интуитивно понимают, почему директива using (например, using namespace A;) вызывает загрязнение в случае воздействия на код, следующий за ней и не осведомленный о наличии этой директивы: поскольку эта директива полностью импортирует одно пространство имен в другое, включая даже те имена, которые до сих пор не были видны, понятно, что это может легко изменить смысл следующего за директивой кода.

Но вот одна распространенная ошибка: многие считают, что использование объявления using на уровне пространства имен (например, using N::Widget;) вполне безопасно. Однако это не так. Такие объявления, как минимум, опасны, причем более тонким и хитрым способом. Рассмотрим следующий код:

// Фрагмент 1

namespace A {

int f(double);

}

 

// Фрагмент 2

namespace B {

using A::f;

void g();

}

 

// Фрагмент 3

namespace A {

int f(int);

}

 

// Фрагмент 4

void В::g() {

f(1); // какая перегрузка будет вызвана?

}

Здесь опасность заключается в том, что объявление using использует текущий список имен f в пространстве имен А в тот момент, когда это объявление встречается. Таким образом, какая именно перегрузка будет видима в пространстве имен В, зависит от того, где именно находится приведенный код фрагментов и в каком порядке он скомбинирован. (Здесь должен раздаться предупреждающий рев вашей внутренней сирены — "Зависимость от порядка есть зло!") Вторая перегрузка, f(int), в большей степени соответствует вызову f(1), но f(int) будет невидима для B::g, если ее объявление окажется после объявления using.

Рассмотрим два частных случая. Пусть фрагменты 1, 2 и 3 находятся в трех различных заголовочных файлах s1.h, s2.h и s3.h, а фрагмент 4 — в файле реализации s4.срр, который включает указанные заголовочные файлы. Тогда семантика B::g зависит от порядка, в котором заголовочные файлы включены в s4.срр! В частности:

• если s3.h идет перед s2.h, то B::g будет вызывать A::f(int);

• иначе если s1.h идет перед s2.h, то B::g будет вызывать A::f(doublе);

• иначе B::g не будет компилироваться вовсе.

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

Давайте теперь рассмотрим ситуацию, когда фрагменты 1, 2, 3 и 4 находятся в четырех различных заголовочных файлах s1.h, s2.h, s3.h и s4.h. Теперь все становится существенно хуже: семантика B::g зависит от порядка включения заголовочных файлов не только в s4.h, но и в любой код, который включает s4.h! В частности, файл реализации client_code.срр может пытаться включить заголовочные файлы в любом порядке:

• если s3.h идет перед s2.h, то B::g будет вызывать A::f(int);

• иначе если s1.h идет перед s2.h, то B::g будет вызывать A::f(doublе);

• иначе B::g не будет компилироваться вовсе.

Ситуация стала хуже потому, что два файла реализации могут включать заголовочные файлы в разном порядке. Что произойдет, если client_code_1.срр включает s1.h, s2.h и s4.h в указанном порядке, a client_code_2.срр включает в соответствующем порядке s3.h, s2.h и s4.h? Тогда B::g нарушает правило одного определения (one definition rule — ODR), поскольку имеются две несогласующиеся несовместимые реализации, которые не могут быть верными одновременно: одна из них пытается вызвать A::f(int), а вторая — A::f(doublе).

Поэтому никогда не используйте директивы и объявления using для пространств имен в заголовочных файлах либо перед директивой #include в файле реализации. В случае нарушения этого правила вы несете ответственность за возможное изменение смысла следующего за using кода, например, вследствие загрязнения пространства имен или неполного списка импортируемых имен. (Обратите внимание на "директивы и объявления using для пространств имен ". Указанное правило неприменимо при описании члена класса с помощью объявления using для внесения, при необходимости, имен из базового класса.)

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

 

Исключения

 

Перенесение большого проекта со старой до-ANSI/ISO реализации стандартной библиотеки (все имена которой находятся в глобальном пространстве имен) к использованию новой (где практически все имена находятся в пространстве имен std) может заставить вас аккуратно разместить директиву using в заголовочном файле. Этот способ описан в [Sutter02].

 

Ссылки

 

[Stroustrup00] §9.2.1 • [Sutter02] §39-40

 



2016-01-26 383 Обсуждений (0)
Храните типы и функции в разных пространствах имен, если только они не предназначены для совместной работы 0.00 из 5.00 0 оценок









Обсуждение в статье: Храните типы и функции в разных пространствах имен, если только они не предназначены для совместной работы

Обсуждений еще не было, будьте первым... ↓↓↓

Отправить сообщение

Популярное:
Почему человек чувствует себя несчастным?: Для начала определим, что такое несчастье. Несчастьем мы будем считать психологическое состояние...
Почему двоичная система счисления так распространена?: Каждая цифра должна быть как-то представлена на физическом носителе...



©2015-2024 megaobuchalka.ru Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. (383)

Почему 1285321 студент выбрали МегаОбучалку...

Система поиска информации

Мобильная версия сайта

Удобная навигация

Нет шокирующей рекламы



(0.012 сек.)