Есть ли преимущество в использовании reference_wrapper для указателя по сравнению с передачей указателя по ссылке? - PullRequest
1 голос
/ 21 марта 2019

В 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, чтобы передать указатель по ссылке. Итак, я начал смотреть вокруг, и я нашел станд :: reference_wrapper . Поэтому я изменил функцию на:

void updateButton(std::reference_wrapper<QAction*> currentButton)
{
   ...
}

и позвонив по:

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

Есть ли какая-то польза от этого, чем QAction *&currentButton?

Ответы [ 3 ]

1 голос
/ 22 марта 2019

Короткий ответ

Нет смысла передавать по ссылке на указатель, а не по указателю, если вы не хотите изменять сам исходный указатель.

Длинный ответ

Основываясь на том, как сформулирован ваш вопрос, а также на вашем первом ответе на комментарии к вашему вопросу, у вас есть фундаментальное неправильное понимание того, что на самом деле являются указателями и ссылками. Вопреки тому, во что вы, кажется, верите, они сами не являются объектами.

Давайте немного вернемся к основам.

Существует две основные области памяти, к которым у нас есть доступ для хранения данных: память стека и память кучи.

Когда мы объявляем переменные, мы получаем выделенное пространство в памяти стека и можем получить доступ к данным, используя имена переменных. Память автоматически освобождается, когда переменная выходит из области видимости.

void myFunction() {
    MyClass instance;         // allocated in Stack memory
    instance.doSomething();

}   // instance gets automatically de-allocated here

Самая большая проблема в том, что объем стековой памяти чрезвычайно ограничен по сравнению с обычными программными потребностями, и тот факт, что вам часто нужны данные для сохранения вне определенных областей, что создание экземпляров больших классов в стековой памяти, как правило, плохая идея. Вот где Куча становится полезной.

К сожалению, с памятью кучи, вам нужно занять время жизни выделения памяти. У вас также нет прямого доступа к данным в памяти кучи, вам нужен своего рода ступенька, чтобы добраться туда. Вы должны явно запросить у ОС выделение памяти, а затем явно сказать ей, чтобы она была выделена, когда вы закончите. 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. Кажется, вы не понимаете, что указатель - это не экземпляр самого объекта, это «ступенька» к объекту. Назначение указателя состоит в том, чтобы удерживать этот адрес памяти, чтобы мы не теряли его и позволяли нам добраться до этой памяти путем разыменования места в памяти.

В 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

Ссылки на указатели являются двойными косвенными указателями, они по сути являются указателями на указатели и полезны, если вы хотите иметь возможность манипулировать не только объектом Heap, но также указателем, содержащим область памяти для этого объекта 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 голосов
/ 21 марта 2019

Если вы передаете указатели в качестве аргументов, почти во всех случаях вы захотите передать их по значению. Производительность будет одинаковой, так как каждый основной компилятор c ++ все равно обрабатывает ссылки как указатели.

Или вы говорите, что думаете, что КОПИЯ ОБЪЕКТА создается, когда вы передаете указатель по значению? Это не так - вы можете передавать указатель столько раз, сколько хотите, новые объекты не будут выделяться.

0 голосов
/ 21 марта 2019

Ваш вопрос состоит из нескольких частей, и я расскажу о каждой из них отдельно.

Сколько стоит указатель?

Передача указателя в основном бесплатна . Если функция, которой вы ее передаете, не имеет слишком много параметров, то компилятор передаст указатель в регистр ЦП, что означает, что значение указателя даже не нужно копировать в стек.

Как дорого обходится по ссылке?

Передача чего-либо по ссылке имеет несколько преимуществ.

  • Ссылка позволяет предположить, что ссылка указывает на действительный объект (поэтому вам не нужно проверять, является ли адрес ссылки нулевым).
  • Когда вы передаете по ссылке, вы можете использовать его как обычный объект; нет необходимости использовать синтаксис ->.
  • Компилятор может (иногда) генерировать более быструю программу, поскольку он знает, что ссылка не может быть переназначена.

Однако, под капотом ссылка передается так же, как указатель . Оба являются просто адресами. И оба в основном бесплатны.

Какой из них вы должны использовать?

  • Если вы передаете большой объект, который вам не нужно изменять, передайте его как постоянную ссылку.
  • Если вы передаете небольшой объект, который вам не нужно изменять, передайте его по значению
  • Если вы передаете ссылку, которую 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;
}
...