C++ Pass by value vs Pass by reference vs Pass by pointers

Rule of thumb: "Use references when you can and pointers when you have to".
When a function is called, the arguments in a function can be passed by value or passed by reference.

Callee is a function called by another and the caller is a function that calls another function (the callee).

The values that are passed in the function call are called the actual parameters.

The values received by the function (when it is called ) are called the formal parameters.

image.png

Passing by Value

Pass by value means that a copy of the actual parameter’s value is made in memory, i.e. the caller and callee have two independent variables with the same value. If the callee modifies the parameter value, the effect is not visible to the caller.

Passes an argument by value.

  • Callee does not have any access to the underlying element in the calling code.
  • A copy of the data is sent to the callee.
  • Changes made to the passed variable do not affect the actual value.
image.png

Passing by value is the most straightforward way to pass parameters. When a function is invoked, the arguments are copied to the local scope of the function. For example,

// class declaration
class Foo {};
void PassByValue(Foo f, int n){
  // Do something with f and n.
}
int main(){
  Foo foo;
  int i = 1;
  PassByValue(foo, i);
}

When PassByValue is invoked in main()’s body, a copy of foo and i is given to PassByValue (copied into PassByValue’s stack frame). This works well for primitive types like ints, floats, pointers, and small classes, but it doesn’t work well for big types. Think std::strings from the STL or if Foo was a really big class with a lot of member variables. Copying these objects would be inefficient if the function doesn’t do a lot of work on these objects. The majority of the CPU time for the function call would be spent on copying the arguments.
Another issue is that in the function call, the program is operating on a copy of the Foo object, not the actual foo object from main(). Any mutations on the object will not be visible in the main() function after PassByValue() completes because all of the work will be discarded once the PassByValue function ends.
These issues can be addressed by passing by pointer or passing by reference.

Pass by reference

Pass by reference (also called pass by address) means to pass the reference of an argument in the calling function to the corresponding formal parameter of the called function so that a copy of the address of the actual parameter is made in memory, i.e. the caller and the callee use the same variable for the parameter. If the callee modifies the parameter variable, the effect is visible to the caller’s variable.

image.png

Overview:

Passes an argument by reference.

  • Callee gives a direct reference to the programming element in the calling code.
  • The memory address of the stored data is passed.
  • Changes to the value have an effect on the original data.

Last but not least, we have pass by reference. Think of a reference variable as another alias for an object. It is a second variable name for the same object. There is no new memory being consumed (except for the variable name) and all operations on the reference variable affect the real underlying object being referred to. For example,

int& count_ref = count;
count++;
count_ref++;
// count is now 2

This is powerful because it allows us to pass large objects into functions by giving another name to the object and not copying the large object, while also allowing us avoid the hairy complications of passing by pointer. A reference variable can never point to null so there doesn’t need to be a null check in every function, and since a reference variable is not heap allocated, there is no worry of a memory leak or figuring out who is responsible for owning and deleting the object.

class Foo {
 public:
  int data[100];
};
void PassByReference(Foo& f, int n){
  // Do something with f and n.
}
int main(){
  Foo foo;
  int i = 0;
  PassByReference(foo, i);
  return 0;
}

Now anything that PassByReference() does to Foo will be still visible in main() after the function ends, but no large objects were copied so the overhead is negligible. Only a reference variable f was created to refer to the foo . Note here that the ampersand does not mean the address-of operator used to retrieve a pointer to an object. Here, it specifies that the variable is a reference variable that refers to an object that already exists. For you C++ language lawyers out there, the address-of operator (&) is an operator that can only be applied to an lvalue, whereas the reference ampersand is used in a declaration specifier sequence.

Pass by Pointer

A pointer is a special type of object that has the memory address of some object. The object can be accessed by dereferencing (*) the pointer, which knows the type of object it is pointing to. Since pointers are memory addresses, they are only 32 or 64 bits so they take up at most 8 bytes. Let’s revisit the previous example but pass Foo by pointer instead.

class Foo {
 public:
  int data[100];
};
void PassByPointer(Foo* f, int n){
  // Do something with *f and n.
}
int main(){
  Foo foo;
  int i = 0;
  PassByPointer(&foo, i);
  return 0;
}

Here, we are passing &foo as the first argument to PassByPointer(). The & operator means “address of” and it is called the address-of operator. Simple, right? It gets more confusing because the & can also denote a reference variable, it depends on the context of where the & is.

So now, foo is not really being copied into the function. The pointer to foo is being copied, and as state above, this is only 8 bytes on most modern computing architectures. This is not a lot of data and can be done in one instruction cycle. Now PassByPointer() can access foo’s member variables by using the dereference operator.

All modifications to the object will also be visible in the main() function after PassByPointer() ends. This may seem like the perfect solution, but it does have its drawbacks.

One drawback with passing by pointer is the lifetime of the pointed to data. The memory location containing the object can change at any time. A common scenario is when the memory location of the object is updated or deleted by another thread. For example, thread A might delete foo and thread B might try to read foo. Depending on the order of execution of threads A and B, there could be a read-after-delete situation, or everything could be fine if the read happens before the delete. This is called a race condition. When multiple threads are accessing the same resource, concurrency primitives such as mutexes must be designed into the program.

There is also a special pointer called nullptr which has value 0x0 and, as the name suggests, points to nothing. Trying to dereference (read from) it will lead to a dreaded segmentation fault. Since nullptr is allowed wherever a function accepts a pointer type, PassByPointer() could be called like PassByPointer(nullptr, 0); . Now, PassByPointer(), as well as every function that has a pointer parameter, needs to do a nullptr check to make sure that it received a valid pointer before dereference it.

void PassByPointer(Foo* f, int n){
  if(f == nullptr) return;
  // Do something with *f and n.
}

As you can see, this can be very tedious and it’s very easy to miss a function in a large codebase.
Another drawback is the concept of ownership. This mostly applies to objects allocated on the heap (i.e. dynamic memory or free store). For example,

  Foo* foo = new Foo();
  int n = 0;
  PassByPointer(foo, n);
  // Do I delete foo here? Or will PassByPointer do it?
  // delete foo;
  return 0;
}

As you may already know, objects allocated on the heap need to be freed before the program ends. In this case, who is responsible for deleting Foo? Is it main()? Or is it PassByPointer()? A programmer would have to look at the implementation of PassByPointer() or maybe the documentation of the function if it is been kept up to date (that is a very big if). If PassByPointer() is taken from a library, the user of the library shouldn’t have to jump around the implementation details to figure out who is responsible for deleting the heap allocated object. Even worse, if the library writers decide to switch the function’s responsibility from not deleting the pointer to deleting the pointer or vice versa, the library user’s code will have a double delete error or memory leak. Nonetheless to say, pointers are tricky but there is hope. In C++11’s standard template library, there is something called a unique pointer which encapsulates the concept of ownership and avoids these problems with ‘raw’ pointers. Unique pointers require knowledge of move semantics and rvalues which I will not cover here. For more information about unique pointers, check out
https://en.cppreference.com/w/cpp/memory/unique_ptr

Good reading materials:
C++ Pass by Value, Pointer*, &Reference

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末利赋,一起剝皮案震驚了整個(gè)濱河市江兢,隨后出現(xiàn)的幾起案子吃谣,更是在濱河造成了極大的恐慌涧狮,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡其掂,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)潦蝇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)款熬,“玉大人,你說(shuō)我怎么就攤上這事攘乒∠团#” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵持灰,是天一觀的道長(zhǎng)盔夜。 經(jīng)常有香客問(wèn)我负饲,道長(zhǎng)堤魁,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任返十,我火速辦了婚禮妥泉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘洞坑。我一直安慰自己盲链,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布迟杂。 她就那樣靜靜地躺著刽沾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪排拷。 梳的紋絲不亂的頭發(fā)上侧漓,一...
    開(kāi)封第一講書(shū)人閱讀 52,268評(píng)論 1 309
  • 那天,我揣著相機(jī)與錄音监氢,去河邊找鬼布蔗。 笑死藤违,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的纵揍。 我是一名探鬼主播顿乒,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼泽谨!你這毒婦竟也來(lái)了璧榄?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤吧雹,失蹤者是張志新(化名)和其女友劉穎犹菱,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體吮炕,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡腊脱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了龙亲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陕凹。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鳄炉,靈堂內(nèi)的尸體忽然破棺而出杜耙,到底是詐尸還是另有隱情,我是刑警寧澤拂盯,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布佑女,位于F島的核電站,受9級(jí)特大地震影響谈竿,放射性物質(zhì)發(fā)生泄漏团驱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一空凸、第九天 我趴在偏房一處隱蔽的房頂上張望嚎花。 院中可真熱鬧,春花似錦呀洲、人聲如沸紊选。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)兵罢。三九已至,卻和暖如春滓窍,著一層夾襖步出監(jiān)牢的瞬間卖词,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工贰您, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拢操,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓舶替,卻偏偏與公主長(zhǎng)得像令境,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子顾瞪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容