Б. Страуструп - Дизайн и Эволюция C++. 2006 (1160775), страница 62
Текст из файла (страница 62)
Можно представить себе систему, в которой для С приняты правила ти побезопаспой компоновки из С++, так что видимое компоновщику имя функции зцгс () будет зо сс р«), Естественно, кодирование типа с помощью добавления суффикса — лишь олин из возможных способов реализации, но он был успешно применен в С1гопс, а затем широко растиражирован. У этого способа есть важные свойства: простота и совместимость с существующими компоновщиками. Такая реализация идеи типобезопасной компоновки не является абсолютно безопасной, но ведь в любом случае лишь очень немногие из полезных систем безопасны на 100%. Более полное описание схемы кодирования имен, примененной в С1гопц приводится в [АКМ, ~7.2с). ИИИИИИИВ Перегрузка работы.
С помощью данного средства выявлялись необнаруженные ошибки в каждой нз больших программ на С и С++, которые мы пытались откомпилировать и связать. К нашему уливлению, некоторые программисты сознательно вносили ошибки в объявления функций, просто чтобы подавить сообщения об ошибках. Например, вызов б (1, а) приводит к ошибке, если й () не объявлена. Я наивно ожидал, что в таком случае программист либо добавит правильное объявление функции, либо включит его в заголовочный файл. Оказалось, что была даже третья возможность — просто поместить любое объявление, не противоречащее вызову: чохб д() ( чоьб Г(1пе ...); 1/ чтобы подавить сообщение об ошибке !/ г(1,а); ) Типобезопасная компоновка вылает сообщение об ошибке, если объявление не соответствует определению.
Также была обнаружена проблема переносимости. Многие объявляли библиотечные функции прямо в коде, вместо того чтобы включить нужный заголовочный файл. Полагаю, что зто делалось для уменьшения времени компиляции, но в результате при переносе на другую систему объявление оказывалось неверным. Типобезопасная компоновка позволила нам выявить целый ряд таких проблем переносимости (в основном между (11ч1Х Бузгеш Ъ' и В81) 1)Н1Х).
Прежле чем остановиться на той схеме, которая включена в язык, был рассмотрен рял других возможностей [8ггоцзггцр, 1988): а не вводить явных директив компоновки, а положиться на инструментальные средства при связывании с С-функциями; а выполнять типобезопасную компоновку и перегрузку только лля функций, явно помеченных ключевым словом очег1оас(; а осуществлять типобезопасную компоновку только лля функций, которые никак не могли быть С-функциями, поскольку их типы нельзя выразить на С.
Опыт использования принятой схемы убедил меня в том, что проблемы, которых я опасался в случае выбора альтернативного решения, были вполне реальными. Например, распространение контроля на все функции стало благом, а программирование на смеси С и С++ оказалось настолько популярным, что любое усложнение совместной компоновки было бы воспринято болезненно. Две особенности вызывали со стороны пользователей нарекания, которые не утихли до сих пор. В первом случае я считаю, что мы бьши правы, а относительно второго не уверен. Функция, объявленная как имеющая С-компоновку, по-прежнему обладает семантикой вызова, принятой в С++. Это значит, что формальные аргументы лолжны быть объявлены, а фактические — соответствовать им с учетом правил сопоставления и разрешения неолнозначности, действующих в С++.
Некоторые пользователи хотели бы, чтобы функции с С-компоновкой подчинялись правилам ~6ИИИИИИП Типобезопасная компоновка гуредеГ чоЫ (*РЧ) (чоЫ*,чогд*); чоЫ* яогг1(чоЫ*, ипя1цпед, РЧ); ехгегп "с" чоЫ* яотг2(чоЫ*, ипятдяед, Рч); Здесь яотс1 () имеет С++-компоновку, принимает указатель на функцию с С++-компоновкои; яогс2 ( ) имеет С-компоновку, принимает указатель на функцию с С++-компоновкой. Это простые случаи. Другой пример; ехгетп "с" гуредег чоЫ (*срч) (чоЫ*,чоЫ*); чоЫ* яогтз(чоЫ*, ппя1дпед, СРЧ); ехгегп "С" чоЫ* яогт4(чоЫ*, ппя1цпед, СРЧ); Здесь яогсЗ () имеет С++-компоновку и принимает указатель на функцию с С-компоновкой; вот г 4 ( ) имеет С-компоновку и принимает указатель на функцию с С-компоновкой. Это почти предел того, что можно выразить в языке.
Альтернативы тоже не очень удачны: можно либо ввести соглашения о вызове в систему типов, либо использовать при вызове переходники для преобразования одних соглашений в другие. Компоновка, межьязыковые вызовы и передача обьектов из одного языка в другой — зто непростые проблемы, у них много аспектов, зависящих от реализации. В данной области правила меняются по мере возникновения новых языков, аппаратных архитектур и методов реализации. вызова С. В таком случае можно было упростить использование заголовочных файлов С.
Но это же позволило бы небрежным программистам вернуться к ослабленному контролю типов, характерному для С, Еще один аргумент против специальных правил для С связан с тем, что другие программисты высказывали такие же просьбы для компоновки с Рааса!, Рог(гав и Р!./1 с поддержкой соответствующих правил вызова. Например, для функций с Рааса!-компоцовкой предлагалось неявно преобразовывать С-строки в Рааса!-строки; для функций с Гогггап-компоновкой — реализовать вызов по ссылке и добавлять информацию о типе массива и т.д.
Если бы мы предоставили специальные возможности для С, то были бы обязаны «наделить» компилятор С++ знанием соглашений о вызове, принятых в огромном количестве языков. Было правильным воспротивиться такому давлению, хотя включенис подобных дополнительных услуг и помогло бы отдельным программистам, работающим на смеси языков. Располагая только лишь семантикой С++, многие обнаружили, что для построения интерфейсов с такими языками, как Рааса! и Рог(гав, где поддерживается передача аргументов по ссылке, полезны ссылки С~-+ (см. раздел 3.7).
С другой стороны, акцентирование внимания только на компоновкс породило определенную сложность. В нашем решении прямо цс рассматривались проблемы среды, поддерживающей программирование на смеси языков, и указатсли на функции с разными соглашениями о вызове. Пользуясь правилами компоновки С++, мы можем непосредственно выразить, каким соглашениям о вызове подчиняется написанная на С или С++ функция. Но нельзя просто сказать, что сама функция подчиняется соглашениям С++, а ее аргументы — соглашениям С.
Можно выразить зто косвенно 1АВМ, стр. 1181, например: ИИИИИИИФ Перегрузка 11.4. Создание и копирование объектов Меня часто просили запретить некоторые операции в языке. Некоторые хотели оптимизировать реализацию классов, для чего необходимо снять разрешение на проведение над объектами классов таких операций, как копирование, наследование и распределение в стеке. В других случаях, когда с помощью объектов представлялись сущности реального мира, для обеспечения требуемой семантики не было нужды во всех операциях, которые поддерживает С++. Ответ на все подобные просьбы был найден во время работы над версией 2.0.
Если вы хотите что-то запретить, сделайтс соотвстствуюшую операцию закрьпой функцией-членом 1см. раздел 2.10). 11 4.1. Контроль допустимости копирования Чтобы запретить копирование объектов класса х, достаточно сделать закрытымп оператор присваивания и копируюШий конструктор. с)авв Х ( Ха орегагог=(сопев ха); О присваивание х(сопев ха); // копирующий конструктор // рпьтгс: Х(1пг); // ); Конечно, внутри функций-членов класса х может копировать объекты данного класса, но в реальных ситуациях это допустимо, а иногда и необходимо. Нс помню, кто первый додумался до этого решения; скорее всего, не я [Ягоцвтгцр, 1986, стр.
172]. Считаю неправильным, что операции копирования определены по умолчанию и во многих своих классах запрещаю копирование объектов. Однако присваивание по умолчанию и копирующие конструкторы перешли в С++ от С и эти возможности часто применяются. 11.4.2. Управление распределением памяти С помошью объявления ряда операций закрытыми можно добиться и других аффектов. Например, если объянить закрытым деструктор, то будет запрещено размещение объектов в стеке и в глобальной памяти, а также случайное употребление с(е1есе: с1авв Оп ггее вгоге ( -Оп тгее вгоге(); // закрытый деструктор // уота г() ( Х а(1); ХЬ=а; Ь=а; ) // правильно: можно создавать объекты класса Х // опибка: Х::х(сопвг Ха) закрыт // овибка: х::орегагог=(сопев ха) закрыт Создание и копирование объектов 4ИИИИИИИ рпЫтс: вгаг(с чогб ггее(оп атее ягоге* р) ( бе1еге р; ) // ); Оп ггее ягоге ц1оЫ; // ошибка: деструктор закрыт чоьб '() ( Оп гее влаге 1ос; // ошибка: деструктор закрыт Оп 'гее ясоге* р = пеы Оп бгее ясоге; // правильно // бе1еге р; // ошибка: деструктор закрыт Оп бгее ясо е:пбгее(р); !/ правильно Разумеется, подобный класс, как правило, будет использоваться вместе с хорошо оптимизированным распределителем свободной памяти или при наличии некоторой семантики, выигрывающей оттого, что объект нахолится в свободной памяти.
Противоположного эффекта — разрешения глобальных и локальных переменных при запрете на размещение объектов в свободной памяти — можно достичь с помощью необычного использования оретасог пеи (): с1аяя Но бгее я оге ( с1аяя опишу ( )' чо1б* орегагог пеы(вьяе Ыопшшу); но ггее ягоге д1о)зз; // правильно чога д() ( но бгее ягоге 1ос; // правильно но бгее вгоге* р = пеы но бгее влаге; // ошибка: // но ггее ягоге::орегагог пеы(яузе г) отсутствует ) 11.4.3.