Is this usage of the const keyword in line with it

2019-06-07 00:09发布

问题:

I am designing an API and am considering the use of "immutable" structs", or "read-only structs". Using a simplified example, this could look something like:

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

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

Everything works fine as long as I return the immutable struct on the stack. However, I run into issues when implementing a function like:

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

At this point, I do not want to introduce a new immutable struct that contains two vectors. I also can not do this:

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

because position and velocity are read-only (although my installed version of clang incorrectly compiles this without warning).

I could use memcpys to work around this, like this:

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);
}

This looks bad but I believe it does not mislead the user of the API, to whom the vector struct still looks immutable. I could additionally add a required initializer for this purpose, where a call to a function like this would only succeed for vector structs with a special value. Something like:

const struct vector * const VECTOR_UNINITIALIZED;

where the user should do

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

before invoking getCurrentPositionAndVelocity(). Before memcpy-ing, the implementation would assert with a memcmp that the vectors do have the "uninitialized sentinel" value. (Typing this, I realize that this will only work if there are certain truly unused values that can act as "uninitialized sentinel" values but I think that is the case for me.)

My question is whether this usage of the const keyword is in line with its intention, from the API user's perspective? And would there be any risks involved from a compiler perspective, in that this code may, strictly speaking, violate the read-only semantics as indicated with the const keyword? As an API user, what would you think of this approach or would you have any better suggestions?

回答1:

TL;DR:

Is this usage of the const keyword in line with its intention?

No.


My question is whether this usage of the const keyword is in line with its intention, from the API user's perspective?

I'm not sure what the API user's perspective is. In fact, a design such as you propose seems likely to produce a larger diversity of user perspective than usual, because the apparent intended behavior is inconsistent with C language requirements.

And would there be any risks involved from a compiler perspective, in that this code may, strictly speaking, violate the read-only semantics as indicated with the const keyword?

Yes. Specifically,


If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined.

(C2011, 6.7.3/6)


Compilers may do all sorts of interesting things when UB occurs. Among the more plausible is that they may assume that some undefined behaviors do not occur. Thus, for example, when compiling a program that calls your getCurrentPositionAndVelocity() function, a compiler might assume that the function does not modify the const members of the structures provided to it. Or the attempt to modify them might actually fail. Or anything else, really -- that's what "undefined" means.

As an API user, what would you think of this approach or would you have any better suggestions?

I would think my API provider should be doing their best to provide an implementation that conforms to the language standard.

Additionally, I would wonder who you think you're protecting me from by making the structure members const, and why you think you know better than I do whether the structure members should be modified. I would also wonder why anyone could possibly think such protection was important enough to warrant all the many problems that attend const structure members.

As for better suggestions, how about just not making the members const? It will save grief for both you and your users.


With respect to the edit adding a reference to How to initialize const members of structs on the heap, the case discussed there is importantly different from the case considered here as far as the standard is concerned. Dynamically-allocated memory has no declared type, but may have an effective type based on what has been written into it and / or how it is accessed. The rules for this are presented in paragraph 6.5/6 of the standard, and you can find discussions of that in several answers elsewhere on SO, such as this one that you linked in comments.

The bottom line is that an object with allocated lifetime gets its effective type from the first data written into it, and that first write can be viewed as its effective initialization (though the latter term is not used in the standard). Subsequent manipulations must respect the effective type, including constraints arising from constness of the type itself or its members, recursively, pursuant to paragraph 6.5/7, colloquially known as the "strict aliasing rule".

Objects with static or automatic lifetime have their declared type as their effective type, and have initial value as specified by their initializers, if any. They, too, must be manipulated in a manner consistent with their effective types.



回答2:

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

You abuse the contract with the compiler. You declare something const and then you try to work it around. Extremely bad practice

Just do not declare struct members as const it you want to violate this agreement.



标签: c struct const