在指针上使用 reference_wrapper 与通过引用传递指针相比有好处吗?
Is there a benefit to using reference_wrapper on a pointer vs passing the pointer by reference?
在 QT 中,当用户单击菜单按钮时,我将 QAction
对象用于 enable/disable 菜单按钮。我突然想到,我为每种情况都编写了相同的功能,所以我将它变成了一个带有私有散列 table 的函数,该函数将对菜单按钮的启用和禁用进行所有控制。换句话说,我的散列 table 看起来像 std::unordered_map<bool,QAction*> table
。其中布尔值是键,对象是我正在更新的值。所以我写了下面的函数:
void updateButton(QAction *currentButton)
{
if(!table.empty())
{
// Enable the last menu item you clicked on and disable the current menu item.
table[false]->setEnabled(true);
currentButton->setEnabled(false);
table[false] = currentButton;
}
else
{
// Menu item was clicked for the first time
currentButton->setEnabled(false);
table[false] = currentButton;
}
}
因此,每当我单击菜单项时。我会在最上面调用这个函数:
void on_action_menuItem1_triggered()
{
updateButton(ui->actionDoAction);
...
}
然后我意识到我正在按值传递指针。而且由于我有太多的按钮,我必须管理,如果可以避免的话,我不想制作任何副本。在这一点上,我完全忘记了我可以做 QAction *¤tButton
来通过引用传递指针。所以我开始环顾四周,我发现
std::reference_wrapper。所以我随后将函数更改为:
void updateButton(std::reference_wrapper<QAction*> currentButton)
{
...
}
并通过以下方式调用它:
updateButton(std::ref(ui->actionDoAction));
这样做比 QAction *¤tButton
有什么好处吗?
你的问题有几个不同的部分,我将分别解决每个部分。
传递一个指针有多昂贵?
传递指针基本上是免费的。如果您将其传递给的函数没有很多参数,那么编译器会将指针传递到 CPU 寄存器中,这意味着指针的值甚至不必复制到堆栈中。
通过引用传递有多昂贵?
通过引用传递一些东西有一些好处。
- 引用允许您假设引用指向一个有效的对象(因此您不必检查引用的地址是否为空)。
- 当您通过引用传递时,您可以像使用普通对象一样使用它;无需使用
->
语法。
- 编译器可以(有时)生成更快的程序,因为它知道无法重新分配引用。
但是,在底层,引用的传递方式与指针的传递方式相同。两者都只是地址。而且两者基本上都是免费的。
你应该使用哪一个?
- 如果您要传递一个您不需要修改的大对象,请将其作为常量引用传递。
- 如果您要传递一个您不需要修改的小对象,按值传递它
- 如果您要传递一个您确实需要修改的引用,请通过引用传递它。
你在修改什么? (按钮,还是指向按钮的指针?)
如果要修改按钮,通过引用传递QAction:
void pushButton(QAction& currentButton) {
currentButton.push();
}
如果要修改 指向按钮的指针,请通过引用传递指针。这是一个不寻常的案例,我不明白你为什么要修改指向按钮的指针。
void modifyPointer(QAction*& buttonPointer) {
buttonPointer++; //I dunno, increment the pointer
}
更好的是,只是 return 修改后的指针:
QAction* modifyPointer(QAction* buttonPointer) {
return buttonPointer + 1;
}
如果您将指针作为参数传递,几乎在所有情况下您都希望按值传递它们。性能将是相同的,因为每个主要的 c++ 编译器在内部都将引用视为指针。
或者您是说您认为按值传递指针时会创建 OBJECT COPY?事实并非如此——你可以随心所欲地传递一个指针,不会分配新的对象。
简答
除非您想修改原始指针本身,否则通过指针引用而不是指针传递没有任何好处。
长答案
根据你提出问题的方式,以及你对问题评论的第一次回复,你对指针和引用的实际含义存在根本性的误解。与您似乎相信的相反,它们并不是对象本身。
让我们回到基础。
为了存储数据,我们可以访问内存中的两个主要区域,堆栈内存和堆内存。
当我们声明变量时,我们会在 Stack 内存中分配 space,我们可以使用变量名访问数据。当变量超出范围时,内存会自动取消分配。
void myFunction() {
MyClass instance; // allocated in Stack memory
instance.doSomething();
} // instance gets automatically de-allocated here
最大的问题是,与正常程序需求相比,Stack 内存的数量极其有限,而且您经常需要数据在某些范围之外持久化,因此在 Stack 内存中创建大型 类 实例是通常是个坏主意。这就是堆变得有用的地方。
不幸的是,对于堆内存,您需要接管内存分配的生命周期。您也无法直接访问堆内存中的数据,您需要一种垫脚石才能到达那里。您必须明确要求 OS 进行内存分配,然后明确告诉它在完成后取消分配内存。 C++ 为此提供了两个运算符:new
和 delete
.
void myFunction(){
MyClass *instance = new MyClass(); // ask OS for memory from heap
instance->doSomething();
delete instance; // tell OS that you don't need the heap memory anymore
}
正如您清楚地理解的那样,在这种情况下实例被称为 pointer
。您似乎没有意识到的是,指针不是对象本身的实例,它是对象的 "stepping stone" 。指针的目的是保存该内存地址,这样我们就不会丢失它,并使我们能够通过取消引用内存位置来访问该内存。
在 C++ 中,有两种方法可以做到这一点:要么取消对整个指针的引用,然后像访问 Stack 上的对象一样访问对象的成员;或者,您将使用 Member Dereferencing 运算符并使用它访问成员。
void myFunction(){
MyClass *instance = new MyClass();
(*instance).doSomething(); // older-style dereference-then-member-access
instance->doSomethingElse(); // newer-style using member dereference operator
}
指针本身只是整数的特例。它们包含的值是您分配对象的堆内存中的内存地址。它们的大小取决于您编译的平台(通常是 32 位或 64 位),因此传递它们并不比传递整数更昂贵。
我怎么强调都不为过,指针变量不是对象本身,它们在堆栈内存中分配,并且在超出范围时表现得与任何其他堆栈变量完全一样。
void myFunction() {
MyClass *instance = new MyClass(); // pointer-sized integer of type 'pointer-to-MyClass' created in Stack memory
instance->doSomething();
} // instance is automatically de-allocated when going out of scope.
// Oops! We didn't explicitly de-allocate the object that 'instance' was pointing to
// so we've lost knowledge of it's memory location. It is still allocated in Heap
// memory but we have no idea where anymore so that memory is now 'leaked'
现在,因为在引擎盖下,指针只不过是特殊用途的整数,传递它们并不比传递任何其他类型的整数更昂贵。
void myFunction(){
MyClass *instance = new MyClass(); // 'instance' is allocated on the Stack, and assigned memory location of new Heap allocation
instance->doSomething();
AnotherFunction(instance);
delete instance; // Heap memory pointed to is explicitly de-allocated
} // 'instance' is automatically de-allocated on Stack
void anotherFunction(MyClass *inst){ // 'inst' is a new pointer-to-MyClass on the Stack with a copy of the memory location passed in
inst->doSomethingElse();
} // 'inst' is automatically de-allocted
到目前为止,我还没有提到引用,因为它们与指针大体上是一样的。它们也只是引擎盖下的整数,但它们通过使成员访问语法与 Stack 变量的语法相同来简化使用。与普通指针不同,引用必须使用有效的内存位置进行初始化,并且该位置不能更改。
以下在功能上是等效的:
MyClass &instance
MyClass * const instance
对指针的引用是双重间接的,它们本质上是指向指针的指针,如果您希望能够操作,不仅是堆对象,还有包含该堆对象的内存位置的指针,它们很有用.
void myFunction(){
QString *str = new QString("First string"); // str is allocated in Stack memory and assigned the memory location to a new QString object allocated in Heap memory
substituteString(str);
delete str; // de-allocate the memory of QString("Second String"). 'str' now points to an invalid memory location
} // str is de-allocated automatically from Stack memory
void substituteString(QString *&externalString){ // 'externalString' is allocated in Stack memory and contains memory location of 'str' from MyFunction()
delete externalString; // de-allocate the Heap memory of QString("First string"). 'str' now points to an invalid location
externalString = new QString("Second string"); // Allocate new Heap memory for a new QString object. 'str' in MyFunction() now points to this new location
} // externalString is de-allocated automatically from Stack memory
如果我已经清楚地解释了自己,并且到目前为止你已经了解了我,那么你现在应该明白,在你的情况下,当你将指向 QAction
的指针传递给函数时,你并不是在复制QAction
对象,您只是将指针复制到该内存位置。由于指针只是引擎盖下的整数,您只是复制 32 位或 64 位大小的东西(取决于您的项目设置),并将其更改为指针引用绝对没有区别。
在 QT 中,当用户单击菜单按钮时,我将 QAction
对象用于 enable/disable 菜单按钮。我突然想到,我为每种情况都编写了相同的功能,所以我将它变成了一个带有私有散列 table 的函数,该函数将对菜单按钮的启用和禁用进行所有控制。换句话说,我的散列 table 看起来像 std::unordered_map<bool,QAction*> table
。其中布尔值是键,对象是我正在更新的值。所以我写了下面的函数:
void updateButton(QAction *currentButton)
{
if(!table.empty())
{
// Enable the last menu item you clicked on and disable the current menu item.
table[false]->setEnabled(true);
currentButton->setEnabled(false);
table[false] = currentButton;
}
else
{
// Menu item was clicked for the first time
currentButton->setEnabled(false);
table[false] = currentButton;
}
}
因此,每当我单击菜单项时。我会在最上面调用这个函数:
void on_action_menuItem1_triggered()
{
updateButton(ui->actionDoAction);
...
}
然后我意识到我正在按值传递指针。而且由于我有太多的按钮,我必须管理,如果可以避免的话,我不想制作任何副本。在这一点上,我完全忘记了我可以做 QAction *¤tButton
来通过引用传递指针。所以我开始环顾四周,我发现
std::reference_wrapper。所以我随后将函数更改为:
void updateButton(std::reference_wrapper<QAction*> currentButton)
{
...
}
并通过以下方式调用它:
updateButton(std::ref(ui->actionDoAction));
这样做比 QAction *¤tButton
有什么好处吗?
你的问题有几个不同的部分,我将分别解决每个部分。
传递一个指针有多昂贵?
传递指针基本上是免费的。如果您将其传递给的函数没有很多参数,那么编译器会将指针传递到 CPU 寄存器中,这意味着指针的值甚至不必复制到堆栈中。
通过引用传递有多昂贵?
通过引用传递一些东西有一些好处。
- 引用允许您假设引用指向一个有效的对象(因此您不必检查引用的地址是否为空)。
- 当您通过引用传递时,您可以像使用普通对象一样使用它;无需使用
->
语法。 - 编译器可以(有时)生成更快的程序,因为它知道无法重新分配引用。
但是,在底层,引用的传递方式与指针的传递方式相同。两者都只是地址。而且两者基本上都是免费的。
你应该使用哪一个?
- 如果您要传递一个您不需要修改的大对象,请将其作为常量引用传递。
- 如果您要传递一个您不需要修改的小对象,按值传递它
- 如果您要传递一个您确实需要修改的引用,请通过引用传递它。
你在修改什么? (按钮,还是指向按钮的指针?)
如果要修改按钮,通过引用传递QAction:
void pushButton(QAction& currentButton) {
currentButton.push();
}
如果要修改 指向按钮的指针,请通过引用传递指针。这是一个不寻常的案例,我不明白你为什么要修改指向按钮的指针。
void modifyPointer(QAction*& buttonPointer) {
buttonPointer++; //I dunno, increment the pointer
}
更好的是,只是 return 修改后的指针:
QAction* modifyPointer(QAction* buttonPointer) {
return buttonPointer + 1;
}
如果您将指针作为参数传递,几乎在所有情况下您都希望按值传递它们。性能将是相同的,因为每个主要的 c++ 编译器在内部都将引用视为指针。
或者您是说您认为按值传递指针时会创建 OBJECT COPY?事实并非如此——你可以随心所欲地传递一个指针,不会分配新的对象。
简答
除非您想修改原始指针本身,否则通过指针引用而不是指针传递没有任何好处。
长答案
根据你提出问题的方式,以及你对问题评论的第一次回复,你对指针和引用的实际含义存在根本性的误解。与您似乎相信的相反,它们并不是对象本身。
让我们回到基础。
为了存储数据,我们可以访问内存中的两个主要区域,堆栈内存和堆内存。
当我们声明变量时,我们会在 Stack 内存中分配 space,我们可以使用变量名访问数据。当变量超出范围时,内存会自动取消分配。
void myFunction() {
MyClass instance; // allocated in Stack memory
instance.doSomething();
} // instance gets automatically de-allocated here
最大的问题是,与正常程序需求相比,Stack 内存的数量极其有限,而且您经常需要数据在某些范围之外持久化,因此在 Stack 内存中创建大型 类 实例是通常是个坏主意。这就是堆变得有用的地方。
不幸的是,对于堆内存,您需要接管内存分配的生命周期。您也无法直接访问堆内存中的数据,您需要一种垫脚石才能到达那里。您必须明确要求 OS 进行内存分配,然后明确告诉它在完成后取消分配内存。 C++ 为此提供了两个运算符:new
和 delete
.
void myFunction(){
MyClass *instance = new MyClass(); // ask OS for memory from heap
instance->doSomething();
delete instance; // tell OS that you don't need the heap memory anymore
}
正如您清楚地理解的那样,在这种情况下实例被称为 pointer
。您似乎没有意识到的是,指针不是对象本身的实例,它是对象的 "stepping stone" 。指针的目的是保存该内存地址,这样我们就不会丢失它,并使我们能够通过取消引用内存位置来访问该内存。
在 C++ 中,有两种方法可以做到这一点:要么取消对整个指针的引用,然后像访问 Stack 上的对象一样访问对象的成员;或者,您将使用 Member Dereferencing 运算符并使用它访问成员。
void myFunction(){
MyClass *instance = new MyClass();
(*instance).doSomething(); // older-style dereference-then-member-access
instance->doSomethingElse(); // newer-style using member dereference operator
}
指针本身只是整数的特例。它们包含的值是您分配对象的堆内存中的内存地址。它们的大小取决于您编译的平台(通常是 32 位或 64 位),因此传递它们并不比传递整数更昂贵。
我怎么强调都不为过,指针变量不是对象本身,它们在堆栈内存中分配,并且在超出范围时表现得与任何其他堆栈变量完全一样。
void myFunction() {
MyClass *instance = new MyClass(); // pointer-sized integer of type 'pointer-to-MyClass' created in Stack memory
instance->doSomething();
} // instance is automatically de-allocated when going out of scope.
// Oops! We didn't explicitly de-allocate the object that 'instance' was pointing to
// so we've lost knowledge of it's memory location. It is still allocated in Heap
// memory but we have no idea where anymore so that memory is now 'leaked'
现在,因为在引擎盖下,指针只不过是特殊用途的整数,传递它们并不比传递任何其他类型的整数更昂贵。
void myFunction(){
MyClass *instance = new MyClass(); // 'instance' is allocated on the Stack, and assigned memory location of new Heap allocation
instance->doSomething();
AnotherFunction(instance);
delete instance; // Heap memory pointed to is explicitly de-allocated
} // 'instance' is automatically de-allocated on Stack
void anotherFunction(MyClass *inst){ // 'inst' is a new pointer-to-MyClass on the Stack with a copy of the memory location passed in
inst->doSomethingElse();
} // 'inst' is automatically de-allocted
到目前为止,我还没有提到引用,因为它们与指针大体上是一样的。它们也只是引擎盖下的整数,但它们通过使成员访问语法与 Stack 变量的语法相同来简化使用。与普通指针不同,引用必须使用有效的内存位置进行初始化,并且该位置不能更改。
以下在功能上是等效的:
MyClass &instance
MyClass * const instance
对指针的引用是双重间接的,它们本质上是指向指针的指针,如果您希望能够操作,不仅是堆对象,还有包含该堆对象的内存位置的指针,它们很有用.
void myFunction(){
QString *str = new QString("First string"); // str is allocated in Stack memory and assigned the memory location to a new QString object allocated in Heap memory
substituteString(str);
delete str; // de-allocate the memory of QString("Second String"). 'str' now points to an invalid memory location
} // str is de-allocated automatically from Stack memory
void substituteString(QString *&externalString){ // 'externalString' is allocated in Stack memory and contains memory location of 'str' from MyFunction()
delete externalString; // de-allocate the Heap memory of QString("First string"). 'str' now points to an invalid location
externalString = new QString("Second string"); // Allocate new Heap memory for a new QString object. 'str' in MyFunction() now points to this new location
} // externalString is de-allocated automatically from Stack memory
如果我已经清楚地解释了自己,并且到目前为止你已经了解了我,那么你现在应该明白,在你的情况下,当你将指向 QAction
的指针传递给函数时,你并不是在复制QAction
对象,您只是将指针复制到该内存位置。由于指针只是引擎盖下的整数,您只是复制 32 位或 64 位大小的东西(取决于您的项目设置),并将其更改为指针引用绝对没有区别。