在指针与通过参考传递指针传递指针上使用Reference_wrapper有好处吗?

Is there a benefit to using reference_wrapper on a pointer vs passing the pointer by reference?

本文关键字:指针 wrapper Reference 参考      更新时间:2023-10-16

在qt中,当用户单击它们时,我使用 QAction对象启用/禁用菜单按钮。在我看来,我在每种情况下都编写相同的功能,因此我将其变成了一个私有哈希表的功能,该功能可以控制菜单按钮的启用和禁用。换句话说,我的哈希表看起来像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 *&currentButton。所以我开始环顾四周,发现STD :: Reference_wrapper。因此,我将功能更改为:
void updateButton(std::reference_wrapper<QAction*> currentButton)
{
   ...
}

并致电:

updateButton(std::ref(ui->actionDoAction));

这样做有任何好处,而不是QAction *&currentButton

简短答案

除非您希望修改原始指针本身,否则通过参考点传递而不是通过指针没有好处。

长答案

基于您的问题的构造方式以及对问题的评论的首次答复,您对提示和参考的实际误解具有根本性的误解。与您似乎相信的相反,它们不是对象本身。

让我们回到基本面。

为了存储数据,堆栈内存和堆内存。

,我们可以访问两个主要区域。

当我们声明变量时,我们可以在堆栈内存中分配空间,并且可以使用变量名称访问数据。当变量从范围掉落时,内存会自动退出。

void myFunction() {
    MyClass instance;         // allocated in Stack memory
    instance.doSomething();
}   // instance gets automatically de-allocated here

最大的问题是,与普通程序需求相比,堆栈内存的数量极为有限,并且您经常需要数据以在某些范围之外持续存在,即在堆栈内存中创建大类实例通常是一个坏主意。那就是堆变得有用的地方。

不幸的是,有了堆内存,您需要接管内存分配的寿命。您也无法直接访问堆内存中的数据,您需要一种垫脚石才能到达那里。您必须明确要求操作系统进行内存分配,然后明确告诉它完成后将内存分配。C 为我们提供了两个操作员:newdelete

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。您似乎没有意识到的是,指针不是对象本身的实例,而是对象的"垫脚石"。指针的目的是持有该内存地址,以便我们不会丢失它,并使我们能够通过删除记忆位置来到达该内存。

在C 中,有两种方法可以做到这一点:您要么删除整个指针,然后像堆栈上的对象一样访问对象的成员;或者,您将使用成员使用运算符并使用它访问成员。

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

到目前为止,我尚未提及参考,因为它们与指针相同。它们也只是整数下的,但是通过使会员访问语法与堆栈变量相同来简化用法。与普通指针不同,必须使用有效的内存位置初始化参考文献,并且该位置无法更改。

以下在功能上等效:

MyClass &instance
MyClass * const instance

引用指针是双指导的,它们本质上是指针的指针,如果您想能够操纵不仅是堆对象,而且还包含包含内存位置到该堆对象的指针。<<<<<<<<<<<<<<<</p>

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位的东西(取决于您的项目设置),然后将其更改为参考点,绝对没有区别。

您的问题有几个不同的部分,我将分别解决每个部分。

通过指针的昂贵?

传递指针是基本上是免费的。如果要将其传递给的函数没有太多参数,则编译器将通过CPU寄存器中的指针传递,这意味着指针的值永远不必复制到堆栈中。

通过参考来传递的昂贵?

通过引用传递的东西有一些好处。

  • 参考允许您假设参考点对一个有效对象(因此您不必检查参考的地址是否为null)。
  • 当您通过引用时,您可以像常规对象一样使用它;无需使用->语法。
  • 编译器可以(有时)生成更快的程序,因为它知道该参考无法重新分配。

但是,在引擎盖下的以与指针为相同的方式传递。两者都是地址。两者基本上都是免费的。

您应该使用哪一个?

  • 如果您传递了一个大对象,需要修改,请将其作为const引用传递。
  • 如果您传递了一个小物体,需要修改,请按值
  • 将其传递
  • 如果您传递了 do 需要修改的参考,请通过参考。

您正在修改什么?(按钮或按钮的指针?)

如果要修改按钮,请通过参考传递QACTION:

void pushButton(QAction& currentButton) {
    currentButton.push(); 
}

如果要修改指针为"按钮" ,请通过参考将指针传递。这是一个不寻常的情况,我不明白您为什么要修改指针到按钮。

void modifyPointer(QAction*& buttonPointer) {
    buttonPointer++; //I dunno, increment the pointer
}

更好,只需返回修改后的指针:

QAction* modifyPointer(QAction* buttonPointer) {
    return buttonPointer + 1;
}

如果您将指针作为参数传递,几乎在所有情况下,您都需要按值传递它们。性能将是相同的,因为每个主要的C 编译器内部都将参考视为指示。

,或者您是说当您按值通过指针时会创建对象副本?事实并非如此 - 您可以根据需要将指针传递到最多,不会分配新的对象。