#c #standards #undefined-behavior #placement-new
#c #стандарты #неопределенное поведение #размещение-новое
Вопрос:
Рассмотрим следующий пример:
#include <new>
struct FunctionObject
{
int operator()() // non-const, modifies the state of this object
{
i = 1;
j = 2;
return i j;
}
int i = 0;
int j = 0;
};
struct Wrapper
{
explicit Wrapper(const FunctionObjectamp; input_object)
{
constructed_object = ::new (buffer) FunctionObject(input_object);
}
~Wrapper()
{
constructed_object->~FunctionObject();
}
int operator()() const // const, but invokes the non-const operator() of the internal FunctionObject
{
return (*constructed_object)(); // this call modifies the internal bytes of this Wrapper
}
alignas(FunctionObject) unsigned char buffer[sizeof(FunctionObject)];
FunctionObject* constructed_object = nullptr;
};
int test()
{
const FunctionObject function_object{3, 4};
const Wrapper object_wrapper{function_object}; // this call modifies the internal bytes of a const Wrapper
return object_wrapper();
}
A Wrapper
содержит внутренний FunctionObject
, который создается внутри с Wrapper
помощью размещения new .
Wrapper
Объект есть const
, его operator()
тоже const
есть, но его вызов вызывает изменение внутреннего состояния объекта. Во многих случаях аналогичные сценарии являются неопределенным поведением в C .
Вопрос в том, является ли это неопределенным поведением в данном конкретном случае (~ мне нужно пометить buffer
как mutable
?), Или стандарт C позволяет писать подобный код?
Комментарии:
1. Я не понимаю, почему это не должно быть UB.
2. Это по сути то же самое, что и объект const, который сохраняет неконстантный указатель на себя во время построения, верно?
3. @user253751 Вроде. С той разницей, что этот объект const создает другой (неконстантный) объект внутри себя и изменяет этот другой объект после построения. Т.Е.
Wrapper
ИзменяетFunctionObject
, но не сам напрямую, хотя этоFunctionObject
находится внутриWrapper
внутренних байтов. Отсюда двусмысленность.
Ответ №1:
Это неопределенное поведение.
Из [dcl.type.cv ],
Любая попытка изменить объект const в течение его жизненного цикла приводит к неопределенному поведению.
Добавление mutable
спецификатора в buffer
позволит изменять его с помощью const
функции-члена.
Ответ №2:
Из [class.mfct.non-static.general/4]:
Может быть объявлена нестатическая функция-член
const
[…]. Эти cv-квалификаторы влияют на типthis
указателя. Они также влияют на тип функции функции-члена; объявленная функцияconst
-член является функцией-членом const […].
Затем из [class.this/1]:
Тип
this
в функции-члене, тип которой имеет cv-qualifier-seq cv и чей классx
является «указателем на cv x». [Примечание 1: Таким образом, в функции-члене const доступ к объекту, для которого вызывается функция, осуществляется через постоянный путь доступа. — конец примечания]
И в конечном итоге из [dcl.type.cv/3-4 ]:
[…] путь доступа с указанием const не может использоваться для изменения объекта […]
Любая попытка изменить объект const в течение его жизненного цикла приводит к неопределенному поведению.
Следовательно, ваша test()
процедура показывает неопределенное поведение, и это даже если ваш объект-оболочка object_wrapper
не const
был.
Ответ №3:
Нет. Это не будет неопределенным поведением из практики, забудьте о том, что описывает «стандарт». Он будет вести себя так же, как и без «const».
Причина в том, что квалификатор «const» для объекта в C просто указывает компилятору генерировать ошибку компиляции в случае, когда вызываются некоторые члены или функции внутри объекта. Кроме того, добавление квалификатора «const» к объекту не приводит к появлению разных двоичных файлов после компиляции.
Это отличается от квалификатора «const», применяемого к примитивным типам, таким как «const int», «const char *». Переменная типа «const int», вероятно, будет заменена ее значением во время компиляции для целей оптимизации. Строковый литерал типа «const char *» будет ссылаться на некоторую страницу памяти, доступ к которой ограничен ОС для чтения таким образом, что изменение содержимого памяти приведет к сбою программы.
Кроме того, квалификатор «const» функции приводит к различным сигнатурам функций и может рассматриваться как перегрузка функции.
Комментарии:
1. спасибо, но я не могу согласиться с этим: «забудьте о том, что описывает «стандарт»» — я бы предпочел этого не делать, если стоимость не составляет ничего, кроме добавления одного дополнительного слова (
mutable
) только в одну строку кода, и таким образом все будут счастливы