包含void*结构的函数的Const正确性和形参

const correctness and parameters to functions with structs containing void*

本文关键字:正确性 形参 Const 函数 结构 包含 void      更新时间:2023-10-16

我不会说我完全理解 const正确性的概念,但至少可以说我理解它。所以,当我遇到这个问题时,我被难住了。有人能给我解释一下吗?考虑下面的代码:

#include <iostream>
struct Test {
    int someInt;
    void * pSomething;
};
void TestFunc(Test * const pStruct) {
    pStruct->someInt = 44;
    pStruct->pSomething = "This is a test string"; // compiler error here
}
int main() {
    Test t;
    TestFunc(&t);
    return 0;
}

在我用注释注释的时候,我从gcc (cygwin的4.5.3)得到了这个错误:

foo.cpp:10:24: error: invalid conversion from `const void*' to `void*'

这显然与结构体包含void*有关,因为一些实验表明,将结构体更改为:

struct Test {
    int someInt;
    char * pSomething;
};

产生一个警告而不是一个错误。同样,保持结构不变,但修改这一行以包含以下强制转换,将编译代码而不发出警告:

pStruct->pSomething = (void*)"This is a test string"; // no error now

我不明白的是,考虑到我对const正确性的理解,为什么编译器会发出这个错误,"从' const void* '到' void* ' 的无效转换"?既然函数定义上的const修饰符使得指针是常量,但它指向的不是,为什么这应该是一个问题?我假设有某种隐式强制转换发生,就像最初写的那样,因为字符串字面值将是像const char *这样的东西,必须转换为void *。尽管如此,问题仍然存在,因为pStruct指向的是而不是常数。

作为参考,我在这里和这里阅读了const正确性。

您观察到的错误与函数声明中使用的const限定符或您在代码中显式使用的任何const限定符完全无关。

问题与下面的最小示例

相同
void *p = "Hello";

也会出现同样的错误。

在c++语言中,字符串字面值的类型是const char [N]。根据const正确性规则,它可以转换为const void *,但不能转换为void *。就是这样。

一个更正式正确的错误信息应该是"不能从const char [22]转换到void *",或者"不能从const char *转换到void *",但显然编译器的内部工作首先执行到const void *的转换(在引线下),然后偶然转换到void *,这就是为什么错误信息是这样写的。

请注意,c++语言的const正确性规则包含一个异常,该异常允许隐式地将字符串字面量转换为char *指针。这就是为什么

char *p = "Hello";
即使像前面的例子一样,

违反了const正确性规则,但编译时也只给出一个警告。该异常仅适用于到char *类型的转换,而不适用于到void *类型的转换,这就是前面的示例产生错误的原因。这种特殊的转换已在c++ 03中弃用,并在c++ 11中从语言中删除。这就是编译器发出警告的原因。(如果你将编译器切换到c++ 11模式,它将成为一个错误)

首先,您的测试类和函数只会使事情变得不必要地复杂。特别是,这个错误与Test * const pStruct中的const无关,因为这只意味着pStruct不能指向其他任何东西。毕竟,用你自己的话来说:

函数定义中的const修饰符使得指针是常量,但它指向的对象不是

下面是重现这个问题的一段简化代码:
int main() {
    void *ptr = "This is a test string"; // error
}

关于你的问题,

我不明白的是,鉴于我对const正确性的理解,是为什么编译器发出这个错误,"无效的转换从"const void*"转换为"void*"?的const修饰符函数定义使得指针是常量,但是它指向的不是,为什么这应该是一个问题?

因为字符串字面值是char const[],"衰减"为char const *,而转换为非常量指针将失去const限定符。

这个不起作用的原因与下面的相同:

int main() {
    int const *i; // what's pointed to by i shall never be modified
    void *ptr = i; // ptr shall modify what's pointed to by i? error!
}

或者更准确地说,

int main() {
    int const i[22] = {}; // i shall never be modified
    void *ptr = i; // ptr shall modify i? error!
}

如果这不是一个错误,那么你可以使用ptr来隐式地绕过iconst限定符。c++根本不允许这样做。

最后,让我们看看这段代码:

pStruct->pSomething = (void*)"This is a test string"; // no error now

同样,这可以用int而不是char来简化和复制,以免混淆实际问题:

int main() {
    int const i[22] = {}; // i shall never be modified
    void *ptr = (void*)i; // ptr shall modify i? ok, I'll just shut up
}

在c++中不应该使用C风格的强制转换。使用static_cast, reinterpret_cast, dynamic_castconst_cast中的一个来明确应该执行哪种转换。

在这种情况下,您已经看到了"关闭"编译器所带来的麻烦:

int main() {
    int const i[22] = {};
    void *ptr = const_cast<void*>(reinterpret_cast<void const *>(i));
}

当然,即使这可以在没有警告的情况下编译,程序的行为也是undefined,因为您不能使用const_cast来抛弃最初初始化为常量的对象的constness。


编辑:我忘记了整个 char *c兼容性业务。但这在其他答案中已经涵盖了,据我所知,我的答案中没有任何错误。

首先,使用C风格强制转换会破坏const的正确性。这是你的演员"有效"的唯一原因。所以不要这么做。使用reinterpret_cast,它(应该,我没有测试它)会得到与您看到的类似的错误。

所以"This is a test string"const char*。如果将其作为void*引用,稍后可能会修改void*的内容。如果你这样做,你就可以对其中的内容进行修改,你就不再是const correct了。

如果没有错误,则可以,如下所示。

int main() {
    Test t;
    TestFunc(&t);
    reinterpret_cast<char*>(t.pSomething)[0]='?';
    return 0;
}

"blah"是一个包含5个char const的数组。在c++ 11中,它隐式转换为char const*。在c++ 03和更早的版本中,字面值也隐式地转换为char*,以兼容C语言,但该转换已被弃用,并在c++ 11中删除。

设置
void* p = "blah";    // !Fails.

得到转换序列char const[5]char const*void*,其中最后一个无效并产生错误。

设置
char* p = "blah";    // Compiles with C++03 and earlier.

在c++ 11中,您得到与void*相同的转换,并且出现错误,但在c++ 03及更早版本中,由于源是字面值字符串,因此您得到char const[5]char* .