在指针上使用reference_wrapper和通过引用传递指针有什么好处?

问题描述 投票:1回答:3

在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

c++ qt pointers pass-by-reference
3个回答
1
投票

简答

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

答案很长

根据您的问题框架的方式,以及您对问题的评论的第一次回复,您对指针和引用实际上是什么有一个基本的误解。与您似乎相信的相反,它们本身并不是对象。

让我们回过头来看一下基本面。

我们可以访问内存中的两个主要区域来存储数据,堆栈内存和堆内存。

当我们声明变量时,我们在Stack内存中获得分配的空间,我们可以使用变量名来访问数据。当变量超出范围时,内存会自动解除分配。

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

到目前为止,我还没有提到引用,因为它们与指针大致相同。它们也只是引擎盖下的整数,但它们通过使成员访问语法与Stack变量的语法相同来简化使用。与普通指针不同,必须使用有效的内存位置初始化引用,并且不能更改该位置。

以下是功能相同的:

MyClass &instance
MyClass * const instance

对指针的引用是双重间接,它们实际上是指向指针的指针,如果您希望能够操作Heap对象,还有包含该堆对象的内存位置的指针,则它非常有用。

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位大小的内容(取决于您的项目设置),并将其更改为引用指针将完全没有区别。


0
投票

你的问题有几个不同的部分,我将分别解决它们中的每一个。

How expensive is passing a pointer?

传递指针基本上是免费的。如果你传递给它的函数没有很多参数,那么编译器会将指针传递给CPU寄存器,这意味着指针的值甚至不必复制到堆栈中。

How expensive is passing by reference?

通过引用传递内容有一些好处。

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

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

Which one should you use?

  • 如果您传递的是不需要修改的大对象,请将其作为const引用传递。
  • 如果您传递的是不需要修改的小对象,请按值传递
  • 如果您传递的是您需要修改的引用,请通过引用传递它。

What are you modifying? (The button, or the pointer to the button?)

如果要修改按钮,请通过引用传递QAction:

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

如果要修改指向按钮的指针,请通过引用传递指针。这是一个不寻常的情况,我不明白你为什么要修改指针的按钮。

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

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

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

0
投票

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

或者你是说你认为在按值传递指针时会创建一个OBJECT COPY?事实并非如此 - 您可以根据需要传递指针,不会分配新对象。

© www.soinside.com 2019 - 2024. All rights reserved.