我遇到了一些我不太了解的事情。让我们假设我想将一个字符指针传递给一个引用void指针的函数。
void doStuff(void*& buffer)
{
// do something
}
我通常会这样做:
int main()
{
unsigned char* buffer = 0;
void* b = reinterpret_cast<void *>(buffer);
doStuff(b);
return 0;
}
为什么不能直接将reinterpret_cast传递给函数?
int main()
{
unsigned char* buffer = 0
// This generate a compilation error.
doStuff(reinterpret_cast<void *>(buffer));
// This would be fine.
doStuff(reinterpret_cast<void *&>(buffer));
return 0;
}
这种行为背后一定有充分的理由,但我没有看到它。
答案 0 :(得分:4)
在第一个例子中,你实际上是在传递指针变量b。所以它有效。
在第二个例子中,第一个reinterpret_cast返回一个指针(按值),它与函数应该得到的引用不匹配,而第二个返回所述引用。
作为向您展示参考如何工作的示例,请查看这两个函数,
void doSomething( unsigned char *ptr );
void doSomethingRef( unsigned char *&ptr );
假设我们有这个指针,
unsigned char *a;
这两个函数的调用方式相同,
doSomething( a ); // Passing pointer a by value
doSomethingRef( a );// Passing pointer a by reference
虽然它可能看起来像是按值传递,但函数会引用它,因此它将作为参考传递。
引用类似于指针,但必须使用左值初始化,且不能为null。
话虽如此,使用void *还有更好的替代方案,特别是void *&amp ;. void *使代码更难阅读,更容易用脚射击自己(如果有的话,让自己使用这些奇怪的演员阵容)。
正如我在评论中所说的那样,你可以使用模板,而不用担心虚空铸造。
template< class T > void doStuff( T *&buffer ) {
...
}
或者,
template< class T > T* doStuff( T* buffer ) {
...
}
编辑:旁注,你的第二个例子是缺少分号,
unsigned char* buffer = 0; // Right here
答案 1 :(得分:1)
int main()
{
unsigned char* buffer = 0;
void* b = reinterpret_cast<void *>(buffer);
doStuff(b);
return 0;
}
b
是一个指针,doStuff(b)
正在接收指针的地址。类型匹配,b
属于void*&
类型(*b
类型为void*
),而且doStuff接收类型为void*&
的参数。
int main()
{
unsigned char* buffer = 0
// This generate a compilation error.
doStuff(reinterpret_cast<void *>(buffer));
// This would be fine.
doStuff(reinterpret_cast<void *&>(buffer));
return 0;
}
第二个调用类似于上面函数的调用,其中b作为参数。
第一个调用仅传递void
指针。类型不同,近距离void*
与void*&
答案 2 :(得分:1)
这就是如何直接将reinterpret_cast指定为函数参数,而不使用中间变量。正如其他人告诉你的那样,这是不好的做法,但我想回答你原来的问题。当然,这仅用于教育目的!
#include <iostream>
void doStuff(void*& buffer) {
static const int count = 4;
buffer = static_cast<void*>(static_cast<char*>(buffer) + count);
}
int main() {
char str[] = "0123456789";
char* ptr = str;
std::cout << "Before: '" << ptr << "'\n";
doStuff(*reinterpret_cast<void**>(&ptr)); // <== Here's the Magic!
std::cout << "After: '" << ptr << "'\n";
}
这里我们有一个名为ptr的char的指针,我们想把它的类型与void *&amp; (对void指针的引用),适合作为参数传递给函数doStuff。
虽然引用是像指针一样实现的,但它们在语义上更像是另一个值的透明别名,因此该语言不能提供操作指针所具有的灵活性。
技巧是:解除引用的指针直接转换为相应类型的引用。
因此,为了获得对指针的引用,我们从指向指针的指针开始:
&ptr (char** - a pointer to a pointer to char)
现在reinterpret_cast的神奇之处使我们更接近目标:
reinterpret_cast<void**>(&ptr) (now void** - a pointer to a void pointer)
最后添加解除引用操作符并完成我们的伪装:
*reinterpret_cast<void**>(&ptr) (void*& - a reference to a void pointer)
这在Visual Studio 2013中编译得很好。这是程序吐出的内容:
Before: '0123456789'
After: '456789'
doStuff函数成功地将ptr提前4个字符,其中ptr是char *,通过引用传递为reinterpret_cast void *。
显然,这个演示有效的一个原因是因为doStuff将指针强制转换为char *以获取更新的值。在实际实现中,所有指针都具有相同的大小,因此在类型之间切换时,您仍然可以避免这种操作。
但是,如果您使用重新解释的指针开始操作指向的值,则可能发生各种不良情况。您也可能违反了“严格别名”规则,因此您也可以将您的名字更改为Mister Undefined Behavior并加入马戏团。怪物。
答案 3 :(得分:0)
我不确定这是否正确,但是......
我相信它与参数类型的匹配很简单:
void doStuff(void* buffer) {
std::cout << reinterpret_cast<char*>(buffer) << std::endl;
return;
}
您可以执行上述操作,int main()
可以正确编译。
引用不同于值的副本 - 不同之处在于复制的值不一定需要存在于变量中或存储在内存中 - 复制的值可能只是堆栈变量而引用不应该指向到期值。一旦你开始使用引用和值语义,这就变得很重要。
tl; dr:在投射时不要混合引用和值。对引用执行操作与对值执行操作不同;即使参数替换是隐含的。