Подтвердить что ты не робот

Можно ли поставить стандартную, чистую директиву заголовка заголовка C внутри пространства имен?

Возможный дубликат:
Можно ли обернуть #include в блоке пространства имен?

У меня есть проект с классом log в глобальном пространстве имен (::log).

Итак, после #include <cmath>, компилятор выдает сообщение об ошибке каждый раз, когда я пытаюсь создать экземпляр объекта моего класса журнала, потому что <cmath> загрязняет глобальное пространство имен множеством трехбуквенных методов, один из них являющейся логарифмической функцией log().

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

  • Переместить класс журнала в собственное пространство имен и всегда обращаться к нему с полным именем. Я действительно хочу избежать этого, потому что регистратор должен быть максимально удобным для использования.
  • Напишите файл mathwrapper.cpp, который является единственным файлом в проекте, который включает <cmath>, и делает все необходимые функции <cmath> доступными через обертки в namespace math. Я не хочу использовать этот подход, потому что мне нужно написать оболочку для каждой требуемой математической функции, и это добавит дополнительный штраф за вызов (частично отмененный флагом -flto)
  • Решение, которое я сейчас рассматриваю:

Заменить

#include <cmath>

по

namespace math {
#include "math.h"
}

а затем вычислить функцию логарифма через math::log().

Я пробовал это, и он действительно компилирует, связывает и работает как ожидалось. Однако он имеет несколько недостатков:

  • Это (очевидно) невозможно использовать <cmath>, потому что код <cmath> обращается к функциям по их полностью определенным именам, и он не рекомендуется использовать в С++.
  • У меня действительно очень плохое чувство, как будто я будет атакован и поглощен живыми хищниками.

Итак, мой вопрос:

  • Есть ли какая-либо рекомендация/соглашение/и т.д., запрещающая вставлять директивы include в пространства имен?
  • Может ли что-то пойти не так с

    • различные стандартные реализации библиотек C (я использую glibc),
    • разные компиляторы (я использую g++ 4.7, -std = С++ 11),
    • сшивание?
  • Вы когда-нибудь пробовали это делать?
  • Есть ли альтернативные способы изгнания математических функций из глобального пространства имен?

Я нашел несколько похожих вопросов о stackoverflow, но большинство из них касалось включения других заголовков С++, которые, очевидно, являются плохими идеями, и те, которые не были сделаны противоречивыми утверждениями о связи поведения для библиотек C. Кроме того, было бы полезно дополнительно поставить #include <math.h> внутри extern "C" {}?

изменить

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

4b9b3361

Ответ 1

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

Эти измененные объявления не будут соответствовать фактическим именам стандартных функций библиотеки, поэтому при времени ссылки ни одна из стандартных функций библиотеки не будет разрешать вызовы функций, объявленных измененными объявлениями, если они не были объявлены extern "C" который разрешен, но не рекомендуется, для имен, которые поступают из стандартной библиотеки C.

ISO/IEC 14882: 2011 17.6.2.2/3 [using.headers] применяется к заголовкам стандартной библиотеки C, поскольку они являются частью стандартной библиотеки С++:

Единица перевода должна включать заголовок только за пределами любой внешней декларации или определения [*] и должна включать заголовок лексически до первой ссылки в этом блоке перевода для любого из объектов, объявленных в этом заголовке.

[*], который будет включать определение пространства имен.

Ответ 2

Почему бы не поставить класс журнала в его собственное пространство имен и использовать typedef namespace::log logger;, чтобы избежать конфликтов имен более удобным способом?

Ответ 3

Измените название своего класса. Не такая большая сделка.; -)

Серьезно, однако, не стоит добавлять имена в глобальное пространство имен, которые сталкиваются с именами из любого стандартного заголовка. С++ 03 явно не разрешал <cmath> определять ::log. Но реализации были хронически несоответствующими этому из-за практичности определения <cmath> поверх существующей <math.h> (и, возможно, также существующей библиотеки статической ссылки для некоторых заголовков, включая математику). Таким образом, С++ 11 ратифицирует существующую практику и позволяет <cmath> сбрасывать все в глобальное пространство имен. С++ 11 также резервирует все эти имена для использования с внешней связью "C" и всеми сигнатурами функций для использования с С++-связью, даже если вы не включаете заголовок. Но об этом позже.

Так как в С++ любой стандартный заголовок может определять имена из любого другого стандартного заголовка (т.е. им разрешено включать друг друга), это означает, что любой стандартный заголовок вообще может определить ::log. Поэтому не используйте его.

Ответ на ваш вопрос о различных реализациях заключается в том, что даже если ваша схема работает с самого начала (что не гарантируется), в какой-то другой реализации может быть заголовок, который вы используете (или хотите использовать в будущем в тот же TU, что и ваш класс журнала), который включает <cmath>, и что вы не дали обработку namespace math. Сверху моей головы <random> мне кажется кандидатом. Он обеспечивает целую кучу непрерывных распределений случайных чисел, которые, вероятно, могут быть реализованы встроенными с математическими функциями.

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

Другая возможность состоит в том, чтобы определить ваш класс как прежде и использовать struct log вместо Log. Это не противоречит этой функции по причинам, которые становятся понятнее, если вы тратите слишком много времени на стандарты C и С++ (вы используете только Log как имя класса, а не как функцию, а не как имя с "C", поэтому вы не нарушаете зарезервированное имя. Несмотря на все проявления обратного, имена классов на С++ все еще хранятся в параллельном юниверсе от других имен, скорее, как теги struct на C).

К сожалению, struct log не является идентификатором простого типа, поэтому, например, вы не можете создать временный код с struct log(VERY_VERBOSE, TO_FILE). Чтобы определить простой тип-идентификатор:

typedef struct log Log;
Log(VERY_VERBOSE, TO_FILE); // unused temporary object

Пример того, что я говорю в комментарии ниже, на основе заявленного примера использования. Я думаю, что это действительно так, но я не уверен:

#include <iostream>
#include <cmath>
using std::log; // to enforce roughly what the compiler does anyway

enum Foo {
    foo, bar
};

std::ostream &log(Foo f) { return std::cout; }

int main() {
    log(foo) << log(10) << "\n";
}

Ответ 4

Это тоже уродливый хак, но я считаю, что это не вызовет проблем с компоновщиками. Просто переопределите имя журнала из <math.h>

 #define log math_log
 #include <math.h>
 #undef log

Это может вызвать проблемы с встроенными функциями из математики, используя этот журнал, но, возможно, вам повезет...

Math log() все еще доступен, но это непросто. Внутри функций, где вы хотите его использовать, просто повторите свое реальное объявление:

    int somefunc() {
        double log(double); // not sure if correct
        return log(1.1);
    }