Является ли изменение внутренних байтов объекта const неопределенным поведением в случае, если он содержит другой объект, созданный путем размещения new ?

#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 ) только в одну строку кода, и таким образом все будут счастливы