Какие стандартные классы c ++ не могут быть переопределены в c ++? - PullRequest
9 голосов
/ 29 октября 2008

Я просматривал планы на C ++ 0x и наткнулся на std::initializer_list для реализации списков инициализаторов в пользовательских классах. Этот класс не может быть реализован в C ++ без использования самого себя, или же с использованием некоторой "магии компилятора". Если бы это было возможно, в этом не было бы необходимости, поскольку любой метод, который вы использовали для реализации initializer_list, можно было бы использовать для реализации списков инициализаторов в вашем собственном классе.

Какие другие классы требуют некоторой формы "магии компилятора" для работы? Какие классы в стандартной библиотеке не могут быть реализованы сторонней библиотекой?

Редактировать: Может быть, вместо того, чтобы реализовать, я должен сказать, экземпляр. Более того, этот класс напрямую связан с языковой функцией (вы не можете использовать списки инициализаторов без initializer_list).

Сравнение с C # может прояснить то, о чем я удивляюсь: IEnumerable и IDisposable на самом деле жестко запрограммированы в языковые функции. Я всегда предполагал, что C ++ свободен от этого, поскольку Страуструп пытался сделать все реализуемым в библиотеках. Итак, есть ли другие классы / типы, которые неразрывно связаны с особенностями языка.

Ответы [ 8 ]

5 голосов
/ 30 октября 2008

std::type_info - простой класс, хотя для его заполнения требуется typeinfo: конструкция компилятора.

Аналогично, исключения являются обычными объектами, но для создания исключений требуется магия компилятора (где выделяются исключения?).

Вопрос для меня: «Как близко мы можем добраться до std::initializer_list s без магии компилятора?»

Глядя на wikipedia , std::initializer_list<typename T> может быть инициализирован чем-то, что очень похоже на литерал массива. Давайте попробуем дать нашему std::initializer_list<typename T> конструктор преобразования, который принимает массив (т.е. конструктор, который принимает один аргумент T[]):

namespace std {
     template<typename T> class initializer_list {
         T internal_array[];
         public:
         initializer_list(T other_array[]) : internal_array(other_array) { };

         // ... other methods needed to actually access internal_array
     }
}

Аналогично, класс, который использует std::initializer_list, делает это, объявляя конструктор, который принимает один std::initializer_list аргумент - a.k.a. конструктор преобразования:

struct my_class {
    ...
    my_class(std::initializer_list<int>) ...
}

Итак, строка:

 my_class m = {1, 2, 3};

Заставляет компилятор думать: «Мне нужно вызвать конструктор для my_class; my_class имеет конструктор, который принимает std::initializer_list<int>; У меня есть литерал int[]; я могу преобразовать int[] в std::initializer_list<int>; и я могу передать это конструктору my_class "( прочитайте до конца ответа, прежде чем сказать мне, что C ++ не позволяет объединять два неявных пользовательских преобразования ).

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

int arry[] = {1, 2, 3};
my_class = arry;

Кроме того, я не стал возиться с ссылками на rvalue.

Наконец, этот класс работает только так, как это говорит новый стандарт, если компилятор неявно объединяет два пользовательских преобразования вместе. Это специально запрещено в обычных случаях, поэтому пример все еще нуждается в магии компилятора. Но я бы сказал, что (1) сам класс является нормальным классом, и (2) задействованная магия (обеспечение синтаксиса инициализации «литерала массива» и возможность неявного связывания двух пользовательских преобразований) меньше, чем кажется первый взгляд.

5 голосов
/ 29 октября 2008

Единственное, о чем я мог подумать, это класс type_info , возвращаемый typeid. Насколько я могу судить, VC ++ реализует это, создавая статически все необходимые классы type_info во время компиляции, а затем просто приводя указатель во время выполнения на основе значений в vtable. Это вещи, которые могут быть выполнены с использованием кода на языке C, но не стандартным или переносимым способом.

1 голос
/ 30 октября 2008

MSalter указывает на printf / cout / stdout в комментарии. Вы можете реализовать любой из них в терминах других (я думаю), но вы не можете реализовать их все вместе без вызовов ОС или магии компилятора, потому что:

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

  2. Они имеют "магическое" поведение во время выполнения, которое, я думаю, не может быть полностью имитировано чистой библиотекой. Например, вы не могли просто использовать статическую инициализацию для создания cout, потому что порядок статической инициализации между модулями компиляции не определен, поэтому не было бы никакой гарантии, что она будет существовать во времени для использования другими статическими инициализаторами. stdout, возможно, проще, поскольку это просто fd 1, поэтому любое поддерживающее его устройство может быть создано с помощью вызовов, на которые оно передается, когда они его видят.

1 голос
/ 30 октября 2008

C ++ позволяет компиляторам определять иначе неопределенное поведение. Это позволяет реализовать Стандартную библиотеку в нестандартном C ++. Например, "onebyone" задается вопросом о atexit (). Авторы библиотек могут предположить, что компилятор делает свои непереносимые C ++ нормальными для своего компилятора.

1 голос
/ 30 октября 2008

Все, что среда выполнения "цепляет" в определенных точках, вероятно, не будет реализовано как переносимая библиотека на гипотетическом языке "C ++, исключая эту вещь".

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

Конечно, вы можете утверждать, что функции C "не учитываются" для этого вопроса. В этом случае std :: surprise может быть лучшим примером по той же причине. Если бы он не существовал, не было бы способа реализовать его, не повредив код исключения, выдаваемый компилятором.

[Редактировать: я только что заметил, что спрашивающий фактически спросил, что классы не могут быть реализованы, а не какие части стандартной библиотеки не могут быть реализованы. Так что на самом деле эти примеры не дают точного ответа на вопрос.]

1 голос
/ 29 октября 2008

Все классы в стандартной библиотеке, по определению, должны быть реализованы в C ++. Некоторые из них скрывают некоторые неясные языковые / компиляторные конструкции, но все еще являются просто обертками вокруг этой сложности, а не языковыми возможностями.

0 голосов
/ 30 октября 2008

Опять же из C ++ 0x, я думаю, что потоки не могут быть реализованы как переносимая библиотека на гипотетическом языке "C ++ 0x, со всеми стандартными библиотеками, кроме потоков".

[Edit: просто чтобы уточнить, кажется, есть некоторые разногласия относительно того, что это значит для «реализации потоков». Что я понимаю, что означает в контексте этого вопроса:

1) Реализовать спецификацию потоков C ++ 0x (какой бы она ни была). Обратите внимание на C ++ 0x, о котором я и спрашивающий говорим. Никакой другой спецификации потоков, такой как POSIX.

2) без "магии компилятора". Это означает, что ничего не нужно добавлять в компилятор, чтобы помочь вашей реализации, и не полагаться на какие-либо нестандартные детали реализации (такие как конкретный макет стека, или средство переключения стеков, или непереносимые системные вызовы для установки временного прерывания ) создать библиотеку потоков, которая работает только для конкретной реализации C ++. Другими словами: чистый, переносимый C ++. Вы можете использовать сигналы и setjmp / longjmp, так как они переносимы, но у меня сложилось впечатление, что этого недостаточно.

3) Предположим, что компилятор C ++ 0x, за исключением того, что в нем отсутствуют все части спецификации потоков C ++ 0x. Если все, чего не хватает, это какая-то структура данных (которая хранит значение выхода и примитив синхронизации, используемый join () или эквивалентным), но присутствует магия компилятора для реализации потоков, то очевидно, что структура данных может быть добавлена ​​как сторонняя портативный компонент. Но это довольно скучный ответ, когда вопрос был о том, какие классы стандартной библиотеки C ++ 0x требуют магии компилятора для их поддержки. ИМО.]

0 голосов
/ 29 октября 2008

Я думаю, что вы достаточно безопасны на этот счет. C ++ в основном служит толстым слоем абстракции вокруг C. Поскольку C ++ является и надмножеством самого C, примитивы базового языка почти всегда реализуются без классов (в стиле C). Другими словами, вы не найдете много ситуаций, таких как Java Object, который представляет собой класс, имеющий особое значение, жестко запрограммированное в компиляторе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...