Соответствует ли это использование ключевого слова const его намерениям? - PullRequest
0 голосов
/ 17 января 2019

Я разрабатываю API и рассматриваю возможность использования "неизменяемых" структур "или" структур только для чтения ". На упрощенном примере это может выглядеть примерно так:

struct vector {
    const float x;
    const float y;
};

struct vector getCurrentPosition(void);
struct vector getCurrentVelocity(void);

Все работает нормально, пока я возвращаю неизменную структуру в стеке. Тем не менее, я сталкиваюсь с проблемами при реализации такой функции, как:

void getCurrentPositionAndVelocity(
    struct vector *position,
    struct vector *velocity);

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

void
getCurrentPositionAndVelocity(
    struct vector *position,
    struct vector *velocity)
{
    *position = getCurrentPosition();
    *velocity = getCurrentVelocity();
}

потому что position и velocity только для чтения (хотя моя установленная версия clang неправильно компилирует это без предупреждения ).

Я мог бы использовать memcpy s, чтобы обойти это, вот так:

void
getCurrentPositionAndVelocity(
    struct vector *position,
    struct vector *velocity)
{
    struct vector p = getCurrentPosition();
    struct vector v = getCurrentVelocity();

    memcpy(position, &p, sizeof *position);
    memcpy(velocity, &v, sizeof *velocity);
}

Это выглядит плохо, но я считаю, что это не вводит в заблуждение пользователя API, для которого структура vector все еще выглядит неизменной. Я мог бы дополнительно добавить требуемый инициализатор для этой цели, где вызов такой функции будет успешным только для vector структур со специальным значением. Что-то вроде:

const struct vector * const VECTOR_UNINITIALIZED;

где пользователь должен сделать

struct vector position = *VECTOR_UNINITIALIZED;
struct vector velocity = *VECTOR_UNINITIALIZED;

перед вызовом getCurrentPositionAndVelocity(). До memcpy -ing реализация с memcmp утверждала бы, что векторы имеют значение "неинициализированный страж". (Набрав это, я понимаю, что это будет работать только в том случае, если есть определенные действительно неиспользуемые значения, которые могут выступать в качестве «неинициализированных дозорных» значений, но я думаю, что это так.)

Мой вопрос заключается в том, соответствует ли использование ключевого слова const его намерениям с точки зрения пользователя API? И будут ли какие-либо риски с точки зрения компилятора, поскольку этот код может, строго говоря, нарушать семантику только для чтения, как указано в ключевом слове const? Как пользователь API, что бы вы подумали об этом подходе или у вас есть предложения получше?

Ответы [ 2 ]

0 голосов
/ 17 января 2019

TL; DR

Соответствует ли использование ключевого слова const его намерениям?

номер


Мой вопрос заключается в том, соответствует ли использование ключевого слова const его намерение, с точки зрения пользователя API?

Я не уверен, какова точка зрения пользователя API. На самом деле, такой дизайн, который вы предлагаете, скорее всего, приведет к большему разнообразию пользовательского подхода, чем обычно, потому что предполагаемое предполагаемое поведение не соответствует требованиям языка C.

И будет ли риски, связанные с точки зрения компилятора, в этом код может строго говоря, нарушать семантику только для чтения, как указано ключевое слово const?

Да. В частности,


Если предпринята попытка изменить объект, определенный с константно-квалифицированный тип посредством использования lvalue с неконстантно-квалифицированным типом тип, поведение не определено.

( C2011, 6.7.3 / 6 )


Компиляторы могут делать всякие интересные вещи, когда возникает UB. Среди наиболее вероятных можно предположить, что некоторые неопределенные варианты поведения не происходят. Таким образом, например, при компиляции программы, которая вызывает вашу функцию getCurrentPositionAndVelocity(), компилятор может предположить, что функция не изменяет члены const структур, предоставленных ей. Или попытка изменить их может фактически потерпеть неудачу. Или что-нибудь еще, действительно - это то, что означает "неопределенный".

Как пользователь API, что бы вы подумали об этом подход или у вас есть предложения получше?

Я думаю, мой провайдер API должен приложить все усилия, чтобы обеспечить реализацию, соответствующую языковому стандарту.

Кроме того, мне было бы интересно узнать, от кого, по вашему мнению, вы меня защищаете, сделав членов структуры const, и почему вы думаете, что вы лучше меня знаете, следует ли изменять элементы структуры. Я также удивляюсь, почему кто-то может подумать, что такая защита была достаточно важной, чтобы оправдать все множество проблем, которые посещают const членов структуры.

Что касается лучших предложений, как насчет того, чтобы просто не делать членов const? Это спасет печаль и для вас, и для ваших пользователей.


Относительно редактирования добавление ссылки на Как инициализировать постоянные элементы структур в куче , обсуждаемый здесь случай существенно отличается от случая, рассмотренного здесь, поскольку Стандарт касается. Динамически распределенная память не имеет объявленного типа, но может иметь эффективный тип, основанный на том, что было записано в него и / или как к нему осуществляется доступ. Правила для этого представлены в параграфе 6.5 / 6 стандарта, и вы можете найти обсуждение этого в нескольких ответах в других разделах SO, таких как этот , который вы связали в комментариях .

Суть в том, что объект с выделенным временем жизни получает свой эффективный тип из первых записанных в него данных, и эта первая запись может рассматриваться как его эффективная инициализация (хотя последний термин не используется в стандарте). Последующие манипуляции должны уважать эффективный тип, включая ограничения, возникающие из const сущности самого типа или его членов, рекурсивно, в соответствии с параграфом 6.5 / 7 , общеизвестным как "правило строгого наложения имен".

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

0 голосов
/ 17 января 2019
memcpy(position, &p, sizeof *position);
memcpy(velocity, &v, sizeof *velocity);

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

Только не объявляйте членов структуры как const, если вы хотите нарушить это соглашение.

...