Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
шпоры по ямп.docx
Скачиваний:
10
Добавлен:
01.04.2025
Размер:
162 Кб
Скачать
☆

14.Пространства имён.

Пространство имён является механизмом отражения логического группирования. То есть если некоторые объявления можно объединить по какому-либо критерию, их можно поместить в одно пространство имён для отражения этого факта.

Пространство имён объявляется следующим образом:

namespace <имя пространства имён>

{ <объявления и определения> }

Нельзя объявить новый член пространства имён вне его определения, используя явный квалификатор. Это делается для того, чтобы можно было найти все имена в определении пространства имён и быстро выявить такие ошибки, как опечатки и несоответствие типов.

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

2.2. Объявления using

Если имя часто используется вне пределов своего пространства имён, неудобно каждый раз использовать квалификатор. Для того чтобы избежать этого, используются объявления using, которые вводят локальные синонимы.

namespace N2

{ ...

using N1::g;

}

// Теперь функцию g можно использовать без квалификатора

Такие синонимы следует делать как можно более локальными во избежание конфликтов имён.

Единственное объявление using делает видимыми все версии перегруженной функции.

2.3. Директивы using

Директива using делает доступными все имена из пространства имён.

namespace N2

{ ...

using namespace N1;

}

// Теперь все функции из пространства имён N1 можно использовать без квалификатора

Можно считать, что директива using является средством композиции пространств имён. В функциях директива using используется для удобства записи. Однако использования глобальных директив using лучше по возможности избегать.

2.4. Неименованные пространства имён

Иногда полезно помещать объявления в пространство имён для того, чтобы исключить возможность конфликта имён. В этом случае можно использовать неименованные пространства имён.

namespace

{ void f() { ... }

void g() { ... }

...

}

Ясно, что должен существовать способ доступа к членам неименованного пространства имён. Следовательно, неименованное пространство имён подразумевает использование директивы using.

namespace XXX

{ void f() { ... }

void g() { ... }

...

}

using namespace XXX;

Здесь XXX является некоторым уникальным именем, которое даётся пространству имён компилятором и не известно программисту.

Неименованные пространства имён в разных единицах компиляции различны. Нельзя осуществить доступ из одной единицы компиляции к члену неименованного пространства имён из другой единицы компиляции.

2.5. Поиск имён

Является скорее правилом, чем исключением, что функция с параметром типа Т определяется в том же пространстве имён, что и Т. Поэтому, если функция не найдена в контексте её использования, осуществляется поиск в пространстве имён её параметров.

namespace NS1

{ class X { ... };

void f(const X &x);

}

void g(NS1::X x)

{ f(x); }

// Используем NS1::f, т.к. тип параметра функции определён в пространстве имён NS1

По сравнению с использованием явных квалификаторов это правило поиска имён экономит время при вводе программы и не приводит к «загрязнению» пространства имён, как это может сделать директива using.

2.6. Псевдонимы пространств имён

Короткие названия пространств имён могут войти в конфликт друг с другом. Однако, длинные названия пространств имён непрактичны при написании реального кода. Эту дилемму можно решить путём создания короткого псевдонима длинного названия пространства имён.

namespace RFBR = Russian_Fund_of_Basic_Researches;

Псевдонимы, кроме того, позволяют пользователю ссылаться на «библиотеку» и в одном единственном объявлении определять реально используемую библиотеку.

namespace Lib = Library_v2r15;

Это может значительно облегчить проблему смены версии библиотеки.

2.7. Объединение и отбор

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

15.Компоновка. Правило одного определения.

3.1. Многофайловые программы

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

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

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

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

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

// file1.cpp

int x = 1;

int b = 0;

extern int c;

// file2.cpp

int x;

extern double b;

extern int c;

В этом примере содержатся три ошибки:

переменная x определена дважды;

переменная b объявлена дважды с различными типами;

переменная c дважды объявлена, но не определена.

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

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

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

// file1.cpp

inline int f(int i) { return i; }

// file2.cpp

inline int f(int i) { return i + 1; }

По умолчанию const и typedef подразумевают внутреннюю компоновку. Поэтому следующий пример допустим, хотя может привести к ошибкам.

// file1.cpp

typedef int T;

const int x = 7;

// file2.cpp

typedef void T;

const int x = 8;

Глобальные переменные, которые являются локальными для одной единицы компиляции, являются типичным источником ошибок, и их лучше избегать. Для обеспечения согласованности лучше помещать глобальные const, typedef и inline только в заголовочные файлы.

Можно заставить константу компоноваться внешним образом путём её явного объявления с помощью ключевого слова extern.

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

В программах на С и старых программах на C++ ключевое слово static используют (что приводит к путанице) для указания «использовать внутреннюю компоновку». Лучше не использовать static кроме как внутри функций и классов.

3.2. Заголовочные файлы

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

Практическое правило гласит, что заголовочный файл может содержать:

именованные пространства имён;

определения типов;

объявления и определения шаблонов;

объявления функций;

определения встроенных функций;

объявления данных;

определения констант;

перечисления;

объявления имён;

директивы включения;

макроопределения;

директивы условной компиляции;

комментарии.

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

определения обычных функций;

определения данных;

неименованные пространства имён.

3.3. Правило одного определения

Каждый конкретный класс, перечисление, шаблон и т.д. должны быть определены в программе ровно один раз.

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

Следовательно, правило стандарта, говорящее о том, что должно существовать уникальное определение класса, шаблона и т.д. должно быть изложено в более сложной форме. Это правило называют «правилом одного определения» (One-Definition Rule, ODR). А именно, два определения класса, шаблона или встроенной функции приемлемы в качестве определения одной и той же сущности тогда и только тогда, когда:

они находятся в различных единицах компиляции;

они идентичны лексема за лексемой;

значения лексем одинаковы в обеих единицах компиляции.

Приведём примеры трёх способов нарушения правила ODR.

// file1.cpp

struct S1 { int a; char b; };

struct S1 { int a; char b; };

// Ошибка – нельзя определить структуру дважды в одной единице компиляции

// file1.cpp

struct S2 { int a; char b; };

// file2.cpp

struct S2 { int a; char bb; };

// Ошибка – имена членов определениях различны

// file1.cpp

typedef int X;

struct S3 { X a; char b; };

// file2.cpp

typedef char X;

struct S3 { X a; char b; };

// Ошибка – смысл X в файлах различен

Проверка согласованности определений классов в различных единицах компиляции, как правило, находится вне пределов возможностей большинства реализаций C++. Как следствие, нарушение правила ODR может являться источником очень тонких ошибок. К сожалению, техника помещения совместно используемых определений в заголовочные файлы с последующим их включением в исходный файл не предохраняет от последней формы нарушения правила. Локальные typedef и макросы могут изменить смысл включаемых объявлений. Наилучшей защитой от подобных проблем является создание как можно более самодостаточных заголовочных файлов.

3.4. Стражи включения

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

Есть два возможных решения этой проблемы:

реорганизовать программу таким образом, чтобы исключить избыточность;

найти метод, допускающий повторное включение заголовочных файлов.

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

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

// file1.h

#ifndef FILE1_H

#define FILE1_H

...

#endif

Содержимое файла между #ifndef и #endif игнорируется компилятором, если FILE1_H определено. В этом случае при первом просмотре файла во время компиляции его содержимое читается, и FILE1_H определяется. Если файл будет включён в единицу компиляции второй раз, его содержимое игнорируется. Метод не идеальный, но он работает и широко распространён в программах на С и C++. Все стандартные заголовочные файлы содержат стражей включения.

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

// file2.h

#define FILE2_H

...

// file2.cpp

#if !defined(FILE2_H)

#include "file2.h"

#endif

Второй вариант немного быстрее, зато первый вариант является самодостаточным.