欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++中stack的pop()函數(shù)返回值解析

 更新時(shí)間:2022年07月25日 14:42:33   作者:code的魅力  
這篇文章主要介紹了C++中stack的pop()函數(shù)返回值,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

stack的pop()函數(shù)返回值

    int temp = s.pop();
    cout<<temp<<endl; 

運(yùn)行代碼會(huì)提示錯(cuò)誤:error C2440: “初始化”: 無法從“void”轉(zhuǎn)換為“int”

全部demo

#include <iostream>
#include <stack>
 
using namespace std;
 
int main()
{
	stack<int> s;
	if(s.empty())
		cout<<"empty"<<endl;   //empty
	s.push(1);
	s.push(6);
	s.push(66);
	cout<<s.size()<<endl;   //3
	int temp = s.pop();
	cout<<temp<<endl;	//66
	cout<<s.size()<<endl;	//2
	cout<<s.top()<<endl;	//6
	cout<<s.size()<<endl;	//2
	system("pause");
	return 0;
 
}

分析

C++中stack,其中有兩個(gè)方法:

  • pop(), 返回void,
  • top(),返回棧頂?shù)囊谩?/li>

所以想要提取棧頂元素,直接用s.top()

C++的返回值優(yōu)化

大家都知道“過早的優(yōu)化是萬惡之源”這句話,然而我相信其中的大多數(shù)人都不知道自己是不是在做過早的優(yōu)化。我也無法準(zhǔn)確的定義什么叫做“過早的優(yōu)化”,但我相信這“過早的優(yōu)化”要么是得不償失的,要么干脆是有害無利的。今天我就想舉個(gè)我認(rèn)為是“過早的優(yōu)化”的例子。

從函數(shù)返回值

為了從一個(gè)函數(shù)得到運(yùn)行結(jié)果,常規(guī)的途徑有兩個(gè):通過返回值和通過傳入函數(shù)的引用或指針(當(dāng)然還可以通過全局變量或成員變量,但我覺得這算不上是什么好主意)。

通過傳給函數(shù)一個(gè)引用或指針來承載返回值在很多情況下是無可厚非的,畢竟有時(shí)函數(shù)需要將多個(gè)值返回給用戶。除了這種情況之外,我覺得應(yīng)當(dāng)盡量做到參數(shù)作為函數(shù)輸入,返回值作為函數(shù)輸出(這不是很自然的事情嗎?)。然而,我們總能看到一些“突破常規(guī)”的做法:

首先定義Message類:

struct Message
{
? ? int a;
? ? int b;
? ? int c;
? ? int d;
? ? int e;
? ? int f;
};

為了從某個(gè)地方(比如一個(gè)隊(duì)列)得到一個(gè)特定Message對象,有些人喜歡寫一個(gè)這樣的getMessage:

void getMessage(Message &msg); // 形式1

雖然只有一個(gè)返回值,但仍然是通過傳入函數(shù)的引用返回給調(diào)用者的。

為什么要這樣呢?“嗯,為了提高性能。你知道,要是這樣定義函數(shù),返回Message對象時(shí)必須要構(gòu)造一個(gè)臨時(shí)對象,這對性能有影響。”

Message getMessage(); // 形式2

我們先不討論這帶來了多少性能提升,先看看形式1相對形式2帶來了哪些弊端。我認(rèn)為有兩點(diǎn):

1. 可讀性變差

略(我希望你能和我一樣認(rèn)為這是顯而易見的)。

2. 將對象的初始化劃分成了兩個(gè)步驟

調(diào)用形式1時(shí),你必然要這樣:

Message msg; ? ? // S1
getMessage(msg); // S2

這給維護(hù)者帶來了犯錯(cuò)的機(jī)會(huì):一些需要在S2語句后面對msg進(jìn)行的操作有可能會(huì)被錯(cuò)誤的放在S1和S2之間。

如果是形式2,維護(hù)者就不可能犯這種錯(cuò)誤:

Message msg = getMessage();

好,現(xiàn)在我們來看性能,形式2真的相對形式1性能更差嗎?對于下面的代碼:

#include <stdio.h>
?
struct Message
{
? ? Message()
? ? {?
? ? ? ? printf("Message::Message() is called\n");?
? ? }
? ? Message(const Message &)
? ? {
? ? ? ? printf("Message::Message(const Message &msg) is called\n");
? ? }
? ? Message& operator=(const Message &)
? ? {
? ? ? ? printf("Message::operator=(const Message &) is called\n");
? ? }
? ? ~Message()
? ? {
? ? ? ? printf("Message::~Message() is called\n");
? ? }
? ? int a;
? ? int b;
? ? int c;
? ? int d;
? ? int e;
? ? int f;
};
?
Message getMessage()
{
? ? Message result;
? ? result.a = 0x11111111;
?
? ? return result;
}
?
int main()
{
? ? Message msg = getMessage();
? ? return 0;
}

你認(rèn)為運(yùn)行時(shí)會(huì)輸出什么呢?是不是這樣:

Message::Message() is called
Message::Message(const Message &msg) is called
Message::~Message() is called
Message::~Message() is called

并沒有像預(yù)期的輸出那樣。

如果使用MSVC2017編譯,且關(guān)閉優(yōu)化(/Od),確實(shí)可以得到預(yù)期輸入,但是一旦打開優(yōu)化(/O2),輸出就和GCC的一樣了。

我們看看實(shí)際上生成了什么代碼(使用GCC編譯):

(gdb) disassemble main
Dump of assembler code for function main():
? ?0x0000000000000776 <+0>:?? ?push ? %rbp
? ?0x0000000000000777 <+1>:?? ?mov ? ?%rsp,%rbp
? ?0x000000000000077a <+4>:?? ?push ? %rbx
? ?0x000000000000077b <+5>:?? ?sub ? ?$0x28,%rsp
? ?0x000000000000077f <+9>:?? ?mov ? ?%fs:0x28,%rax
? ?0x0000000000000788 <+18>:?? ?mov ? ?%rax,-0x18(%rbp)
? ?0x000000000000078c <+22>:?? ?xor ? ?%eax,%eax
? ?0x000000000000078e <+24>:?? ?lea ? ?-0x30(%rbp),%rax ? ? ? ? ? ? #將棧上地址-0x30(%rbp)傳給getMessage函數(shù)
? ?0x0000000000000792 <+28>:?? ?mov ? ?%rax,%rdi
? ?0x0000000000000795 <+31>:?? ?callq ?0x72a <getMessage()>
? ?0x000000000000079a <+36>:?? ?mov ? ?$0x0,%ebx
? ?0x000000000000079f <+41>:?? ?lea ? ?-0x30(%rbp),%rax
? ?0x00000000000007a3 <+45>:?? ?mov ? ?%rax,%rdi
? ?0x00000000000007a6 <+48>:?? ?callq ?0x7e4 <Message::~Message()>
? ?0x00000000000007ab <+53>:?? ?mov ? ?%ebx,%eax
? ?0x00000000000007ad <+55>:?? ?mov ? ?-0x18(%rbp),%rdx
? ?0x00000000000007b1 <+59>:?? ?xor ? ?%fs:0x28,%rdx
? ?0x00000000000007ba <+68>:?? ?je ? ? 0x7c1 <main()+75>
? ?0x00000000000007bc <+70>:?? ?callq ?0x5f0 <__stack_chk_fail@plt>
? ?0x00000000000007c1 <+75>:?? ?add ? ?$0x28,%rsp
? ?0x00000000000007c5 <+79>:?? ?pop ? ?%rbx
? ?0x00000000000007c6 <+80>:?? ?pop ? ?%rbp
? ?0x00000000000007c7 <+81>:?? ?retq ??
End of assembler dump.
(gdb) disassemble getMessage?
Dump of assembler code for function getMessage():
? ?0x000000000000072a <+0>:?? ?push ? %rbp
? ?0x000000000000072b <+1>:?? ?mov ? ?%rsp,%rbp
? ?0x000000000000072e <+4>:?? ?sub ? ?$0x20,%rsp
? ?0x0000000000000732 <+8>:?? ?mov ? ?%rdi,-0x18(%rbp) ? ? ? ? ? ? ? ? #將main函數(shù)傳入的棧上地址保存到-0x18(%rbp)處
? ?0x0000000000000736 <+12>:?? ?mov ? ?%fs:0x28,%rax
? ?0x000000000000073f <+21>:?? ?mov ? ?%rax,-0x8(%rbp)
? ?0x0000000000000743 <+25>:?? ?xor ? ?%eax,%eax
? ?0x0000000000000745 <+27>:?? ?mov ? ?-0x18(%rbp),%rax ? ? ? ? ? ? #將main函數(shù)傳入的棧上地址傳給Message::Message()函數(shù)
? ?0x0000000000000749 <+31>:?? ?mov ? ?%rax,%rdi
? ?0x000000000000074c <+34>:?? ?callq ?0x7c8 <Message::Message()>
? ?0x0000000000000751 <+39>:?? ?mov ? ?-0x18(%rbp),%rax
? ?0x0000000000000755 <+43>:?? ?movl ? $0x11111111,(%rax)
? ?0x000000000000075b <+49>:?? ?nop
? ?0x000000000000075c <+50>:?? ?mov ? ?-0x18(%rbp),%rax
? ?0x0000000000000760 <+54>:?? ?mov ? ?-0x8(%rbp),%rdx
? ?0x0000000000000764 <+58>:?? ?xor ? ?%fs:0x28,%rdx
? ?0x000000000000076d <+67>:?? ?je ? ? 0x774 <getMessage()+74>
? ?0x000000000000076f <+69>:?? ?callq ?0x5f0 <__stack_chk_fail@plt>
? ?0x0000000000000774 <+74>:?? ?leaveq?
? ?0x0000000000000775 <+75>:?? ?retq ??
End of assembler dump.

可以看出來,在getMessage函數(shù)中構(gòu)造的對象實(shí)際上位于main函數(shù)的棧幀上,并沒有額外構(gòu)造一個(gè)Message對象。這是因?yàn)殚_啟了所謂的返回值優(yōu)化(RVO,Return Value Optimization)的緣故。你想得到的效果編譯器已經(jīng)自動(dòng)幫你完成了,你不必再犧牲什么。

RVO

對于我們這些用戶來說,RVO并不是什么特別復(fù)雜的機(jī)制,主流的GCC和MSVC均支持,也沒什么特別需要注意的地方。它存在的目的是優(yōu)化掉不必要的拷貝復(fù)制函數(shù)的調(diào)用,即使拷貝復(fù)制函數(shù)有什么副作用,例如上面代碼中的打印語句,這可能是唯一需要注意的地方了。從上面的匯編代碼中可以看出來,在GCC中,其基本手段是直接將返回的對象構(gòu)造在調(diào)用者棧幀上,這樣調(diào)用者就可以直接訪問這個(gè)對象而不必復(fù)制。

RVO是有限制條件的,在某些情況下無法進(jìn)行優(yōu)化,在一篇關(guān)于MSVC2005的RVO技術(shù)的文章中,提到了3點(diǎn)導(dǎo)致無法優(yōu)化的情況:

1. 函數(shù)拋異常

關(guān)于這點(diǎn),我是有疑問的。文章中說如果函數(shù)拋異常,開不開RVO結(jié)果都一樣。如果函數(shù)拋異常,無法正常的返回,我當(dāng)然不會(huì)要求編譯器去做RVO了。

2. 函數(shù)可能返回具有不同變量名的對象

Message getMessage_NoRVO1(int in)
{
? ? Message msg1;
? ? msg1.a = 1;
?
? ? Message msg2;
? ? msg2.a = 2;
?
? ? if (in % 2)
? ? {
? ? ? ? return msg1;
? ? }
? ? else
? ? {
? ? ? ? return msg2;
? ? }
}

經(jīng)過驗(yàn)證,在GCC上確實(shí)也是這樣的,拷貝構(gòu)造函數(shù)被調(diào)用了。但這種情況在很多時(shí)候應(yīng)該都是可以通過重構(gòu)避免的。

Message::Message() is called
Message::Message() is called
Message::Message(const Message &msg) is called
Message::~Message() is called
Message::~Message() is called
Message::~Message() is called

3. 函數(shù)有多個(gè)出口

Message getMessage_NoRVO2(int in)
{
? ? Message msg;
? ? if (in % 2)
? ? {
? ? ? ? return msg;
? ? }
? ? msg.a = 1;
? ? return msg;
}

這個(gè)在GCC上驗(yàn)證發(fā)現(xiàn)RVO仍然生效,查看匯編發(fā)現(xiàn)只有一個(gè)retq指令,多個(gè)出口被優(yōu)化成一個(gè)了。

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • C/C++編譯報(bào)錯(cuò)printf was not declared in this scope問題及解決

    C/C++編譯報(bào)錯(cuò)printf was not declared in 

    這篇文章主要介紹了C/C++編譯報(bào)錯(cuò)printf was not declared in this scope問題及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-08-08
  • C語言變量類型與輸出控制用法實(shí)例教程

    C語言變量類型與輸出控制用法實(shí)例教程

    這篇文章主要介紹了C語言變量類型與輸出控制用法,是C語言程序設(shè)計(jì)中比較基礎(chǔ)也是比較重要的用法,需要的朋友可以參考下
    2014-08-08
  • C語言創(chuàng)建和操作單鏈表數(shù)據(jù)結(jié)構(gòu)的實(shí)例教程

    C語言創(chuàng)建和操作單鏈表數(shù)據(jù)結(jié)構(gòu)的實(shí)例教程

    這篇文章主要介紹了C語言創(chuàng)建和操作單鏈表數(shù)據(jù)結(jié)構(gòu)的實(shí)例教程,講解使用C語言實(shí)現(xiàn)鏈表結(jié)構(gòu)時(shí)指針的使用,需要的朋友可以參考下
    2016-04-04
  • C++類的定義與實(shí)現(xiàn)

    C++類的定義與實(shí)現(xiàn)

    這篇文章主要介紹了C++類的定義與實(shí)現(xiàn),違章圍繞C++類的定義的相關(guān)資料展開全文內(nèi)容,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-01-01
  • 淺談c語言中類型隱性轉(zhuǎn)換的坑

    淺談c語言中類型隱性轉(zhuǎn)換的坑

    下面小編就為大家?guī)硪黄獪\談c語言中類型隱性轉(zhuǎn)換的坑。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-08-08
  • 利用Matlab繪制有趣圖像的示例代碼

    利用Matlab繪制有趣圖像的示例代碼

    這篇文章主要為大家總結(jié)了一些利用Matlab繪制的有趣好看的圖像的示例代碼。文中的示例代碼簡潔易懂,感興趣的小伙伴可以動(dòng)手試一試
    2022-03-03
  • C語言數(shù)據(jù)結(jié)構(gòu) 棧的基礎(chǔ)操作

    C語言數(shù)據(jù)結(jié)構(gòu) 棧的基礎(chǔ)操作

    這篇文章主要介紹了C語言數(shù)據(jù)結(jié)構(gòu) 棧的基礎(chǔ)操作的相關(guān)資料,需要的朋友可以參考下
    2017-05-05
  • C語言用遞歸函數(shù)對素?cái)?shù)進(jìn)行判斷流程

    C語言用遞歸函數(shù)對素?cái)?shù)進(jìn)行判斷流程

    素?cái)?shù)判斷是編程語言學(xué)習(xí)過程中一個(gè)老生常談的話題,而它的實(shí)現(xiàn)也有多種算法,包括經(jīng)典的試除法(以及試除法的幾種優(yōu)化),進(jìn)階的素?cái)?shù)表篩選法,埃拉托斯特尼篩法和歐拉篩法(以及它們的優(yōu)化)等。對以上算法感興趣的朋友們,不妨搜索“素?cái)?shù)判斷的N種境界”來學(xué)習(xí)了解
    2022-09-09
  • C語言經(jīng)典例程100例(經(jīng)典c程序100例)

    C語言經(jīng)典例程100例(經(jīng)典c程序100例)

    這篇文章主要介紹了C語言經(jīng)典例程100例,經(jīng)典c程序100例,學(xué)習(xí)c語言的朋友可以參考一下
    2018-03-03
  • C++中訪問權(quán)限的示例詳解

    C++中訪問權(quán)限的示例詳解

    C++通過 public、protected、private 三個(gè)關(guān)鍵字來控制成員變量和成員函數(shù)的訪問權(quán)限(也稱為可見性),下面這篇文章主要給大家介紹了關(guān)于C++中訪問權(quán)限的相關(guān)資料,需要的朋友可以參考下
    2021-07-07

最新評論