C/С++ Редкие ключевые слова - регистр, volatile, extern, explicit
Можете ли вы дать мне быстрый обзор того, для чего используются эти 4 ключевых слова и почему?
Я понимаю основы, которые Google расскажет вам о регистрации и нестабильности, но хотел бы узнать немного больше (просто практический обзор). Extern и явным образом путают меня немного, так как я никогда не нашел причины, чтобы использовать их самостоятельно, несмотря на то, что делал довольно низкоуровневый код встроенных систем. Опять же, я могу google, но я бы предпочел бы быстрое, практическое резюме от эксперта, так что это все в моих мыслях.
Ответы
Ответ 1
ехЬегп
extern
перегружен для нескольких целей. Для глобальных переменных это означает, что он объявляет переменную, а не определяет ее. Это полезно для размещения глобальных переменных в заголовках. Если вы поместите это в заголовок:
int someInteger;
Каждый .cpp файл, который включает этот заголовок, будет пытаться иметь свой собственный someInteger
. Это вызовет ошибку компоновщика. Объявив его extern
, вы говорите только, что в коде будет someInteger
:
extern int someInteger;
Теперь в файле .cpp вы можете определить int someInteger
, так что будет только одна его копия.
Существует также extern "C"
, который используется для указания того, что определенные функции используют правила C-linkage, а не С++. Это полезно для взаимодействия с библиотеками и кодом, скомпилированным как C.
В С++ 0x также будут объявления extern template
. Это противоположность явного создания шаблона. Когда вы это сделаете:
template class std::vector<int>;
Вы говорите компилятору, чтобы создать экземпляр этого шаблона прямо сейчас. Обычно создание экземпляра задерживается до первого использования шаблона. В С++ 0x вы можете сказать:
extern template class std::vector<int>;
Это говорит компилятору не создавать экземпляр этого шаблона в этом .cpp файле, никогда. Таким образом, вы можете контролировать, где создаются шаблоны. Разумное использование этого может существенно улучшить время компиляции.
Явный
Это используется для предотвращения автоматических преобразований типов. Если у вас есть класс ClassName
со следующим конструктором:
ClassName(int someInteger);
Это означает, что если у вас есть функция, которая принимает ClassName
, пользователь может вызвать ее с помощью int
, и преобразование будет выполнено автоматически.
void SomeFunc(const ClassName &className);
SomeFunc(3);
Это законно, потому что ClassName
имеет конструктор преобразования, который принимает целое число. Это то, как функции, которые принимают std::string
, также могут принимать char*
; std::string
имеет конструктор, который принимает a char*
.
Однако большую часть времени вам не нужны неявные преобразования, подобные этому. Вы обычно хотите, чтобы преобразования были явными. Да, это иногда полезно, например, с std::string, но вам нужен способ отключить его для недопустимых конверсий. Введите explicit
:
explicit ClassName(int someInteger);
Это предотвратит неявные преобразования. Вы все еще можете использовать SomeFunc(ClassName(3));
, но SomeFunc(3)
больше не будет работать.
BTW: если explicit
для вас редко, то вы не используете его почти достаточно. Вы должны использовать его всегда, если вы специально не хотите конверсии. Это не так часто.
летучий
Это предотвращает определенные полезные оптимизации. Обычно, если у вас есть переменная, C/С++ предполагает, что ее содержимое изменится только в том случае, если оно явно изменит их. Поэтому, если вы объявляете int someInteger;
как глобальную переменную, компиляторы C/С++ могут кэшировать значение локально, а не постоянно получать доступ к значению каждый раз, когда вы его используете.
Иногда вы хотите остановить это. В этих случаях вы используете volatile
; это предотвращает эти оптимизации.
Регистр
Это всего лишь намек. Он сообщает компилятору попытаться поместить переменные данные в регистр. Это по существу не нужно; компиляторы лучше, чем вы решаете, что должно и не должно быть регистром.
Ответ 2
register
используется как подсказка для компилятора о том, что переменная должна храниться в регистре, а не в стеке. Компиляторы часто игнорируют это и делают все, что захотят; переменные будут выделены в регистры, если это вообще возможно.
volatile
указывает, что память может измениться, если программа фактически ничего не делает. Это еще один намек на компилятор, что он должен избегать оптимизации доступа к этому местоположению. Например, если у вас есть две последовательные записи в одно и то же место без промежуточных чтений, компилятор может оптимизировать первый. Однако, если местоположение, на которое вы пишете, является аппаратным регистром, вам нужно, чтобы каждая запись проходила точно так же, как написано. Так что volatile
- это как сказать "просто поверьте мне на это".
extern
указывает, что определение происходит за пределами текущего файла. Полезно для глобальных переменных, но обычно подразумевается в декларации. Как отмечает Блинди, это также полезно для указания функции, которая должна иметь C-связь. Функция с C-связью будет скомпилирована с использованием ее фактического имени в качестве ее символа в выходном исполняемом файле. Функции С++ включают в себя больше информации, например, типы их аргументов в своих символах. Вот почему перегрузка работает в С++, но не в C.
explicit
применяется к конструкторам С++. Это означает, что конструктор не следует называть неявным. Например, скажем, у вас есть класс Array
с конструктором, который принимает целое число capacity
. Вы не хотите, чтобы целочисленные значения были неявно преобразованы в объекты Array
только потому, что существует целочисленный конструктор.
Ответ 3
register
в основном игнорируется в наши дни, но он должен намекнуть компилятору, что вы предпочли бы, чтобы переменная была в регистре, а не в стеке. Оптимизаторы делают такую хорошую работу в эти дни, что теперь это не имеет значения.
volatile
сообщает компилятору не предполагать, что значение изменяется только от текущего потока, поэтому он всегда будет считывать фактическое значение памяти, а не кэшировать его между чтениями. Это полезно для многопоточных приложений, но вам все равно лучше использовать примитивы ОС (события, семафоры и т.д.).
extern
не определяет значение, оно только объявляет его. Он в основном используется для экспорта и импорта функций из DLL или разделяемых библиотек (.a
). Побочный эффект также позволяет отключить манипуляцию имени для С++ с помощью extern "C"
.
И explicit
позволяет указать, что конструктор должен быть явным, в отличие от неявных конструкторов преобразования (где вы можете написать CMyClass val=10;
, если он имеет неявный конструктор, который принимает int
).
Ответ 4
Регистр используется для указания компилятору использовать регистр для хранения этого значения, современные компиляторы оптимизируют его использование во время циклов for-loops и т.д.
volatile - это varaibles, которые могут быть изменены внешним процессом или во время многопоточных приложений.
extern сообщает компоновщику, что переменная определена в другом файле.
Явное указание компилятору не допускать неявных преобразований типа.
Ответ 5
1: Регистр используется, когда вы хотите принудительно сохранить значение в регистре, а не хранить его в ОЗУ. Например, вы можете сделать следующее:
register int x;
Это позволит компилятору знать, что вы хотите, чтобы этот int был помещен в регистр CPU, что должно дать вам быстрый доступ к нему. Тем не менее, вашему компилятору разрешено игнорировать это ключевое слово в процессе оптимизации, и в большинстве случаев хорошие компиляторы будут помещать переменные в регистры, если они должны быть в любом случае. Это, в основном, избыточное ключевое слово, на мой взгляд.
2: Летучие подсказки компилятору о том, что переменная будет изменяться регулярно, например, если вы определите некоторый float как volatile:
volatile float flt;
Это говорит компилятору, что вы хотите выполнить оптимизацию, характерную для постоянно меняющихся переменных. В нем также указывается, что переменная может меняться без ввода активной программы. Опять же, ключевое слово redundant в настоящее время (за исключением многопоточного программирования).
3: Extern сообщает компилятору, что определение находится в другом файле, к которому была объявлена переменная, например, у вас может быть общий файл заголовка, который вы включаете в большинство ваших файлов, здесь вы можете захотеть объявить некоторые глобальные указатели, поэтому вы бы сделали следующее:
extern MyClass* g_pClassPointer;
Затем вы переходите к объявлению в верхней части файла cpp, ваш MyClass
реализован в:
MyClass* g_pClassPointer = nullptr;
Ключевое слово extern также используется для объявления компилятору, что вы используете исходный код C или код ASM, например, вы можете сделать следующее:
extern __asm {
mov eax, 2
mov ebx, 3
add eax, ebx
}
Или, если вы просто хотите использовать исходный код C, вы можете использовать extern "C"
, и ваш компилятор это узнает.
4: Явный используется, если вы не хотите неявного преобразования внутри конструктора, для получения дополнительной информации см. этот поток. Он в основном используется для целей отладки и для обеспечения выполнения более строгих правил при выполнении ООП.