Hazel引擎学习(二)
我自己維護引擎的github地址在這里,里面加了不少注釋,有需要的可以看看
一. Precompiled Headers
為了避免頭文件被反復編譯,需要加上pch文件,主要有以下幾點
- 需要在VS工程里添加hzpch.h和hzpch.cpp文件,前者放所有常用的庫的頭文件,對于后者,一般的pch是不需要cpp文件的,但是VS工程里需要這個東西,所以得加上,然后讓他引用hzpch.h
- 然后在premake5.lua文件里進行修改,添加兩個參數,pchheader “…” 和 pchsource “…” ,后者一般只是VS工程需要,其他平臺會忽略這個,再次Build工程后,項目屬性配置里會顯示,使用pch
- 最后再把所有用到基本庫的cpp(或者說所有cpp)里,都加上#include "hzpch.h"
自己動手,出了兩個錯誤,這兩個問題,如果我項目屬性中勾選不使用pch都不會報錯。
第一個是報錯:
Severity Code Description Project File Line Suppression State Error C1083 Cannot open precompiled header file: '..\bin-int\Debug-windows-x86_64\Hazel\Hazel.pch': No such file or directory Hazel c:\hazel\hazel\src\hazel\application.cpp 2對比了一下視頻,發現是自己的premake5.lua文件里的pchsource參數寫的不對,我是這么寫的:
pchheader "hzpch.h"pchsource "hzpch.cpp" -- 一般只有Visual Studio還要求pchsource,其他的IDE會自動忽略這一行結果就報了上述問題的錯誤,實際上pchheader可以不顧當前路徑隨便寫,但是cpp文件得明確指定路徑,需要改成:
pchsource "%{prj.name}/Src/hzpch.cpp" -- 根目錄基于premake5.lua文件關鍵是我這么寫也是報錯,報錯如下,如果我把project屬性里的使用pch選項關掉,就不會發生下述報錯:
 
 經過仔細排查,發現由于粗心,導致頭文件的包含順序出錯了,一定要記得把pch文件放到cpp的include的最上面,不然很可能出現編譯錯誤,所以我的錯誤點如下圖所示,順序反了:
 
第二個問題是報錯:
Severity Code Description Project File Line Suppression State Error C1010 unexpected end of file while looking for precompiled header. Did you forget to add '#include "hzpch.h"' to your source? Hazel c:\hazel\hazel\source.cpp 1我發現任何項目中的cpp文件好像都要Include這個pch文件,即使這個cpp文件里什么內容也沒有。
總結: 一定要記得,使用pch時,include的操作一定要在第一行,而且,如果勾選了使用pch文件,好像所有的cpp文件都必須include該文件
二. Add Glfw Library
glfw庫就不多介紹了,學過OpenGL的都知道,可以實現跨平臺窗口繪制,與之前的spdlog庫一樣,Hazel引擎使用glfw庫作為submodule,額外的操作就是,需要使用glfw庫的premake5.lua文件build工程,然后利用該工程創建的lib文件,這一節的重點就是如何實現premake5.lua的嵌套和相關工程的鏈接
大致思路如下:
- git submodule add url path
- 修改premake5.lua文件,需要include glfw庫的premake5.lua文件,這個機制很像C++的頭文件include機制,就是純粹的copy and paste
- 還要使Hazel引擎Link glfw庫生成的Lib文件
關于修改premake5.lua文件,首先介紹兩個命令,include和includedirs,感覺就跟C++里的include和additional Include Directories兩個功能很像,寫法如下:
------- include ------ -- runs "src/MyApplication/premake5.lua" include "src/MyApplication"-- runs "my_script.lua" just once, lua的include應該自帶pragma once的效果 include "my_script.lua" include "my_script.lua"------- includedirs ------ -- 會在以下目錄里尋找premake5.lua文件 -- paths specifies a list of include file search directories. -- Paths should be specified relative to the currently running script file. includedirs { "paths" }-- Define two include file search paths. includedirs { "../lua/include", "../zlib" }-- You can also use wildcards to match multiple directories. The * will match against a single directory, -- ** will recurse into subdirectories as well. includedirs { "../includes/**" }順便提一下,Cherno在premake5.lua里創建了一個類似于C++的Struct類型,具體代碼如下:
IncludeDirs = {} IncludeDirs["GLFW"] = "Hazel/vendor/GLFW/include"實際使用的時候,就可以當作宏來使用,如下所示:
includedirs {"%{IncludeDirs.GLFW}" }三. 基于GLFW庫,創建Hazel引擎的Window類和Event類
在做這個事情之前,下面這些概念都得熟悉:Vsync、Observe Pattern、回調函數、函數指針、Event相關名詞。
 同時我也學到了很多小知識,一些不是很重要的東西都放在文章結尾,以免影響整體流程閱覽
 
Event類
 Window類與Event類是緊密結合的東西,先看看Hazel的Event基類是怎么設計的:
然后舉一個具體的例子,比如實現按鍵盤的事件:
class HAZEL_API KeyPressedEvent : public Event{public:KeyPressedEvent(int keycode, int keyRepeated):m_Keycode(keycode), m_KeyRepeated(keyRepeated) {}inline int GetRepeated() const { return m_KeyRepeated; }std::string ToString()const override{std::stringstream ss;ss << "KeyPressedEvent:\n KeyCode : " << m_Keycode << " KeyRepeated: " << m_KeyRepeated;return ss.str();}static EventType GetStaticType() {return EventType::KeyPressed; } // 此類下的Event類型都是一樣的,所以應該設為Staticvirtual EventType GetEventType() const override {return GetStaticType();} // 寫這個是為了防止沒有KeyEvent類型,只有Event類型virtual const char* GetName() const override { return "KeyPressed"; } protected:int m_KeyRepeated;int m_Keycode;};Window類
 Window類作為接口類,需要包含通用的窗口內容:
- 虛析構函數
- 一個Update函數,用于在loop里每幀循環
- 窗口的長和寬,以及相應的Get函數
- 設置窗口的Vsync和Get窗口的Vsync函數
- 窗口的回調函數,當窗口接受事件輸入時,會調用這個回調函數
所以Windows接口類設計如下:
class HAZEL_API Window{public:// Window自帶一個回調函數,用來處理從glfw庫收到的callbackusing EventCallbackFn = std::function<void(Event&)>;virtual ~Window() {};virtual float const& GetWindowHeight() const = 0;virtual float const& GetWindowWidth() const = 0;virtual bool IsVSync() const = 0;virtual void SetVSync(bool) = 0;virtual void OnUpdate() = 0;virtual void SetEventCallback(const EventCallbackFn& callback) = 0;static Window* Create(const WindowProps& props = WindowProps());};應該設計一個Application類,由Application創建window,同時Application給與window對應的回調函數,讓window接受glfw的回調函數后,再來調用對應Application的回調函數,而window本身是不知道Application的存在的,設計代碼如下
class HAZEL_API Application{public:Application();virtual ~Application(){}void OnEvent(Event& e);void Run();bool OnWindowClose(WindowCloseEvent& e);private:std::unique_ptr<Window>m_Window;bool m_Running = true;};Application* CreateApplication();具體實現代碼如下:
Application::Application(){m_Window = std::unique_ptr<Window>(Window::Create());m_Window->SetEventCallback(std::bind(&Application::OnEvent, this, std::placeholders::_1));// 設置window的callback為此對象的OnEvent函數// 像下面這樣直接寫lambda也是可以的//m_Window->SetEventCallback([](Event& e)->void//{// if (e.GetEventType() == EventType::MouseScrolled)// {// MouseScrolledEvent ee = (MouseScrolledEvent&)e;// LOG( "xOffset:{0} and yOffset:{1}", ee.GetXOffset(), ee.GetYOffset());// }//}//);}void Application::OnEvent(Event& e){//CORE_LOG("{0}", e);CORE_LOG(e.ToString());EventDispatcher dispatcher(e);dispatcher.Dispatch<WindowCloseEvent>(std::bind(&Application::OnWindowClose, this, std::placeholders::_1));}void Application::Run() {std::cout << "Run Application" << std::endl;while (m_Running){// Application并不應該知道調用的是哪個平臺的window,Window的init操作放在Window::Create里面// 所以創建完window后,可以直接調用其loop開始渲染m_Window->OnUpdate();}//LOG(w.ToString());}bool Application::OnWindowClose(WindowCloseEvent &e){m_Running = false;return true;}其中,EventDispatcher用于根據事件類型的不同,調用不同的函數:
// 當收到Event時,創建對應的EventDispatcherclass HAZEL_API EventDispatcher {template<typename T>using EventHandler = std::function<bool(T&)>;//EventHandler存儲了一個輸入為任意類型的引用,返回值為bool的函數指針public:EventDispatcher(Event& event):m_Event(event){}// T指的是事件類型, 如果輸入的類型沒有GetStaticType會報錯template<typename T>void Dispatch(EventHandler<T> handler){if (m_Event.m_Handled)return;if (m_Event.GetEventType() == T::GetStaticType()) {m_Event.m_Handled = handler(*(T*)&m_Event); //使用(T*)把m_Event轉換成輸入事件的指針類型}}private:Event& m_Event;//必須是引用,不可以是Event的實例,因為Event帶有純虛函數};四. 附錄
Vsync
垂直同步旨在解決畫面的fps與顯示器的幀率不同步的問題,當二者存在差距時,可能會出現Screen Tearing(屏幕撕扯)的情況,如下圖所示:
 
 下面的圖示展示了GPU和顯示器繪制幀率不同步導致屏幕撕扯的情況:
 
使用Vsync的好處:
- 降低GPU的幀率,使其跟顯示器的幀率一樣
- 對于一些老游戲,GPU幀率會特別高,這很消耗性能,而開啟垂直同步能同步GPU幀率到顯示器的幀率,從而減少GPU的計算量
使用Vsync的壞處:
- 可能會感受到Input輸入,比如鼠標和鍵盤輸入的Delay,如下圖所示的區間輸入鍵盤會延遲到下一幀
 什么時候適合用Vsync:
 當GPU幀率大于顯示器的時候用Vsync會比較好,如果GPU幀率本來就比較低,垂直同步反而會起到反作用,具體的還有一些顯卡里面的特定類型的Vsync,這里就不再詳細介紹了。
相關的OpenGL函數如下所示:
// This function sets the swap interval for the current OpenGL or OpenGL ES context // i.e. the number of screen updates to wait from the time glfwSwapBuffers was called // before swapping the buffers and returning. // This is sometimes called vertical synchronization, // vertical retrace synchronization or just vsync. // [in] interval:The minimum number of screen updates to wait for until the buffers are swapped by glfwSwapBuffers. void glfwSwapInterval(int interval)也就是說,這個函數決定了調用glfwSwapBuffers函數的頻率,interval代表幀數,所以設定是否開啟垂直同步的函數應該這么寫:
void SetVsync(bool enabled) {if(enabled)glfwSwapInterval(1);elseglfwSwapInterval(0); }虛析構函數與純虛析構函數
基類的析構函數必須是虛函數,否則子類的析構函數會去調用基類的析構函數而不是調用自身的析構函數。首先復習一下相關的內容,如果析構函數不為虛函數,下面這個情況還是正常的:
#include<iostream>class Base { public:Base(){ std::cout << "Base Constructor" << std::endl; }~Base() { std::cout << "Base Destructor" << std::endl; } };class Derived : public Base { public:Derived(){ std::cout << "Derived Constructor" << std::endl; }~Derived() { std::cout << "Derived Destructor" << std::endl; } };int main() {Base *base = new Base();delete base;std::cout << "-------------\n";Derived *derived = new Derived();delete derived;std::cin.get(); }結果是正常的,派生類會先調用基類的構造函數,后調用基類的析構函數,輸出如下
Base Constructor Base Destructor ------------- Base Constructor Derived Constructor Derived Destructor Base Destructor但如果我們這么寫,會發現派生類的析構函數沒有調用:
int main() {Base *base = new Derived();delete base;std::cin.get(); }輸出如下:
Base Constructor Derived Constructor Base Destructor這樣就會出問題,比如在派生類的Constructor里new了內存,在其析構函數里進行delete,如果這里沒有設置virtual析構函數,就會造成memory leak
那么問題來了,基類的析構函數需要聲明為虛函數,但是有沒有必要聲明成純虛函數呢?
 首先,C++是支持純虛析構函數的,兩種寫法都可以
純虛函數也就是接口函數,如果類里聲明了純虛析構函數,那么該類還要負責定義這個函數,所以純虛析構函數也一樣,但值得注意的是,一般函數的純虛函數不需要實現,但是純虛析構函數一定要提供函數的實現。
 (one of the most important things to remember is that if a class contains a pure virtual destructor, it must provide a function body for the pure virtual destructor.)
為什么要這么做呢?因為虛析構函數與普通的虛函數不同,虛析構函數并不是簡單的override,就拿上面的例子來說,B繼承A,當B消失時,會先調用B的析構函數,再調用A的析構函數,如果是一般的虛函數,則不會調用原來的函數,只會調用override之后的函數。所以需要這么寫:
class Base { public:Base(){ std::cout << "Base Constructor" << std::endl; }virtual ~Base() = 0; }; Base::~Base() { std::cout << "Base Destructor" << std::endl; } // 必須實現純虛析構函數這種寫法就跟直接聲明virtual的析構函數是一樣
所以純虛析構函數有什么用呢,想想純虛函數有什么用就知道了,純虛函數一般都是為了接口設計的,所以這么寫,可以禁止直接創建Base基類的對象,當然如果基類里面本來就含有純虛函數作為接口,那么純虛析構函數意義也不大了。
觀察者模式(Observe Pattern)
這種模式常見于窗口系統的設計和游戲開發領域,舉個例子,如下圖所示,當主角移到周圍的格子時,如何做出合適的反應:
 
 我第一反應想的是,在主角的移動函數里做判斷,大概是這么寫:
這樣寫的缺點有兩個:
- 角色類與加血、襲擊和困住這些事件類的耦合太高
- 如果加一個新的事件,還要去修改Agent類的代碼,這不適合拓展
還有一種方法,就是加血這些事件類,每隔一段時間就判斷主角是否在其范圍內,這樣會造成CPU消耗,也不好。
 所以Observe Pattern能很好的解決這個問題,當角色觸發這個事件時,能第一時間讓所有可能響應的事件收到這個消息,就好像一個主播,給所有的訂閱者發送推送一樣。
為了降低主角類與事件類的耦合性,設置一個規定,所有主角這種類(或者說類似于主播這種會發送通知的類),需要創建一個存放所有訂閱者的數據結構(ObserverList),再規定幾個統一的接口,用于添加訂閱,取消訂閱,發送訂閱,就是以下內容:
 
 而所有的observer類,需要定義一個接口,作為收到消息時的響應函數,這里就叫做Update函數好了,所以寫出來兩個接口代碼是這樣是:
對于這些響應的類,只要確保實現了update函數就可以了:
//陷阱 public class Trap implements Observer {@Overridepublic void update() {if(inRange()){System.out.println("陷阱 困住主角!");}}private boolean inRange(){//判斷主角是否在自己的影響范圍內,這里忽略細節,直接返回truereturn true;} }對于主角類,只需要在移動的時候,調用notifyAllObservers就行了:
public class Hero extends Subject{void move(){System.out.println("主角向前移動");notifyObservers();} }而創建主播類與用戶類之間連接的代碼則是在運行程序里進行執行的,代碼如下:
public class Client {// 在游戲執行時建立這些連接public static void main(String[] args) {//初始化對象Hero hero = new Hero();Monster monster = new Monster();Trap trap = new Trap();Treasure treasure = new Treasure();//注冊觀察者hero.attachObserver(monster);hero.attachObserver(trap);hero.attachObserver(treasure);//移動事件hero.move();} }這樣寫代碼又簡潔,又容易拓展,對于設計Event系統來說非常好用。關于觀察者模式的這些內容參考于Github精英社區公眾號,寫的很好。
關于__debugbreak
方便使用引擎來判斷數據類型是否為null,創建了一個Assert類型的宏,為了創建這個宏,需要知道兩個宏的操作,一個是Variadic Arguments(可變的參數),這個之前也用到過了,對應宏的參數是__VA_ARGS__,第二個就是一個新的宏,叫做__debugbreak
The __debugbreak compiler intrinsic, similar to DebugBreak, is a portable Win32 way to cause a breakpoint.相當于C#里調試的Debug.break(),調試的時候如果執行到這里會自動打斷點,如下圖所示:
 
所以這么寫就可以了:
#ifdef HZ_ENABLE_ASSERTS#define HAZEL_ASSERT(x, ...) if(!x) {LOG_ERROR("Assertion Failed At: {0}", __VA_ARGS__);\__debugbreak();}#define HAZEL_CORE_ASSERT(x, ...) if(!x) {CORE_LOG_ERROR("Assertion Failed At: {0}", __VA_ARGS__);\__debugbreak();} #else#define HAZEL_ASSERT(x, ...) // 非Debug狀態下這行代碼毫無作用#define HAZEL_CORE_ASSERT(x, ...) #endif5.關于Callback
- Callback function are nothing but a function pointer that are used to call particular functions on a software event.
- Simply pointing a function address to that function pointer is called as registering a callback function .
由上可知,C語言實現callback的本質就是函數指針。但是看完了這一段,我還是不太明白,到底這個callback是在哪callback
OK,下面舉一個例子:
#include<iostream>void funcA() {std::cout << "funcA\n"; }void funcB(void(*ptr)()) {ptr(); }int main() {funcB(funcA);std::cin.get(); }可以看到,funcA是作為一個函數指針傳給了嗎funcB,所以在funcB里,B函數接受了函數A作為參數,然后又在B里去調用A,
 再進一步,可以把函數B做一個拓展:
作為用戶而言,使用函數B的時候,是輸入了函數A,但是并不立馬調用函數A,而是執行其他的操作,直到需要的時候再回來調用函數A,這里就是callback叫法的得來了,其中這里的funcA就叫做callback函數,而funcB就叫做對應callback函數的register函數
通過指針函數,或者說callback函數,還可以實現一下功能,如下所示,Compare函數是用戶輸入的函數,需要指定compare的方式:
 
function pointer、function handle和callback function
 function pointer和function handle基本是同一個東西,可能用了typedef聲明了對應的handle,而callback function是指的那些被用作為callback的函數,實際上就是輸入的函數指針對應的函數,
 function handles and function pointers are the same thing. They hold a reference to a function. A callback function is a fancy name for a function used as a callback. Often a callback function is specified as a handle or pointer to the actual function, depending on language.
如何設計Event System
經過上面的解釋,callback到底是什么應該很清楚了,但是具體到怎么應用到Windows的Event里,我還是不太清楚。
首先明晰兩個概念:Event和EventHandler,這兩個術語取自.NET events
- event handler: A holder for an actual function that should be called when we raise the notification.
- event: A holder for a number of handlers. An event can be called for raising a notification (and call its handlers).
 簡單來說,event handler存儲了一個函數,用來處理事件,而event存儲了很多對應的handler,當對應event事件發生時會調用對應的eventhandler
EventListener
 EvetnListener這個名詞,相當于Observe Pattern里的Observer,對于一個事件,事件發生時,所有監聽該事件的EventListenner就是觀察者模式里的觀察者,這個時候Observer會調用自己的Update函數,所以這里我們這么設計:
EventHandler
 感覺這個概念跟EventListener好像,前面提到了,其實跟EventListener用途是一樣的,都是存放了用來處理事件的函數或函數指針。
 二者的核心區別在這里:A listener watches for an event to be fired.The handler is responsible for dealing with the event.
 EventListener在乎事件是否被觸發,而EventHandler在乎的是事件觸發后如何被處理。可以看一段代碼:
https://blog.kotlin-academy.com/programmer-dictionary-event-listener-vs-event-handler-305c667d0e3c
EventDispatcher
 Event產生時,需要根據Event類型來決定進行什么操作,這一段內容顯然不應該由Event類來執行,所以需要創建一個EventDispatcher類,來處理這個判斷的問題,每當一個Event產生時,都會創建一個對應的EventDispatcher,負責將該Event傳遞給對應的EventHandler,所以說,EventDispatcher是用來連接EventHandler和Event的,大概代碼如下:
基類里的static
在不考慮變量訪問權限的情況下,基類里的static變量被所有的派生類共享,基類里的static函數同樣也被所有的派生類共享,所以對于Window基類,創建了統一的創建window的函數:
class Window{ public:...static Window* Create(const WindowProps& props); // 所有的子類Window都共享函數,具體實現在子類里實現,適配不同平臺 }protected constructor
以前提到過兩種方法,能夠禁止一個類被實例化,一是private constructor,二是pure virtual function
但是最近看到了一種寫法,就是protected constructor,具體有什么用呢?
該用法在于,對于抽象基類,如果把其constructor設置為protected,那么,該基類雖然不能被用戶直接進行實例化,但聲明為protected能讓該基類的子類被實例化,舉個代碼的例子:
class Base1 { public:virtual ~Base1() {} protected:Base1() {} };class Base2 { public:virtual ~Base2() {}Base2() {} };int main() {Base1 *base1 = new Base1(); //編譯錯誤,Base1的構造函數是protected,不可以訪問Base2 *base2 = new Base2(); //編譯成功 }所以聲明protected constructor for base class,就是保證基類的構造函數,只能在其派生類中調用,這種基類一般是abstract類,但又不是接口類
static函數的定義,以及unique_ptr和make_unique
之前C#寫多了,搞得C++的static函數定義不會寫了,C++的static成員在聲明時需要加上static關鍵字,但是在定義的時候就不要再加上這個了,可以看下面這個例子:
// 在WindowsWindow.cpp中,我本來是想實現基類Windows里聲明的static 創建窗口的函數的 // 但是我是這么寫的,以下是WindowsWindow里的函數實現 static Hazel::Window* Create(const WindowProps& props = WindowProps()) {return new WindowsWindow(props); }上面的代碼,我犯了兩個錯誤,這樣就導致我直接創建了一個屬于WindowsWindow類的static函數:
- 如果想要定義原本已經聲明好的static函數,那么前面就不要再加static了
- 我的函數代碼簽名錯了,Create函數應該是基類Window的,所以前面應該寫Window::Create
下面才是正確的寫法
Hazel::Window* Window::Create(const WindowProps& props = WindowProps()){return new WindowsWindow(props);}而引起上述的問題的,是下面一段代碼
Application::Application(){//m_Window = std::unique_ptr<Window>(Window::Create()); // 成功編譯m_Window = std::make_unique<Window>(Window::Create()); // 編譯錯誤}make_unique的本意是不使用new關鍵字,主要是為了安全考慮
foo(make_unique<T>(), make_unique<U>()); // exception safe foo(unique_ptr<T>(new T()), unique_ptr<U>(new U())); // unsafe*所以我猜應該是因為我的Window::Create()是使用的new來創建的變量,這在make_unqiue里面應該是不允許的
不要在頭文件里Include該頭文件本身用不到的其他頭文件
比如說我的app程序引用了游戲引擎的dll,那么我其實只要知道其API的聲明就可以了,并不需要知道其底層的結構,更不需要include <GLFW/glfw3.h>,所以這些頭文件也不應該include具體實現的庫是什么,比如說我的app引入了WindowsWindow.h,那么WindowsWindow.h包含了函數的聲明,會被導到DLL中,但具體實現是在WindowsWindow.h中,所以include <GLFW/glfw3.h>應該放在WindowsWindow.cpp中,因為用戶不應該需要知道glfw3.h的頭文件定義,況且,glfw3.h內容也很大。
std::bind用法
 也沒什么難的,就是用std::bind把一個函數和它的參數綁定起來,形成一個wrapper,舉幾個簡單例子:
可以看到std::bind的返回類型是不可知的,所以上面的類型都用auto來表示,實際上std::bind一般是與std::function一起使用的,std::bind返回的是一個callable object,代碼如下所示:
void f(int a, int c) { cout << c << endl; } int main() {const auto f1 = std::bind(&f, std::placeholders::_2, std::placeholders::_1);std::function<void(int, int)>f2(f1);f2(4,3); // Print 4cin.get(); }std::function用法
下面這幾句有啥區別
using EventCallbackFn = std::function<void(Event&)>(Event&); //1 using EventCallbackFn = std::function<void(Event&)>; //2 using EventCallbackFn = std::function<void>(Event&); //3上面的1和3的寫法都是錯的,主要還是不熟悉std::function的寫法,比如說std::function<void(int)>s,s被稱作callable object,可以是以下內容:
- 函數指針
- Lambda表達式
- std::bind返回的東西(std::bind一般都與std::function一起用)
- Functor: 仿函數,在我理解就是Object里實現了()符號的重載
- 類的成員函數
- 類的靜態函數
具體舉幾個例子:
#include <functional> #include <iostream>struct Foo {Foo(int num) : num_(num) {}void print_add(int i) const { std::cout << num_ + i << '\n'; }int num_; };void print_num(int i) {std::cout << i << '\n'; }struct PrintNum {void operator()(int i) const{std::cout << i << '\n';} };int main() {// store a free functionstd::function<void(int)> f_display = print_num;f_display(-9); // print -9// store a lambda, lambda的返回類型can be deduced, 而且沒有參數, 所以類型為void()std::function<void()> f_display_42 = []() { print_num(42); };f_display_42(); // print 42// store the result of a call to std::bind, 將函數與特定參數綁定std::function<void()> f_display_31337 = std::bind(print_num, 31337);f_display_31337(); // print 31337// store a call to a member functionstd::function<void(const Foo&, int)> f_add_display = &Foo::print_add;const Foo foo(314159);f_add_display(foo, 1); // print 314160f_add_display(314159, 1); // print 314160// store a call to a data member accessorstd::function<int(Foo const&)> f_num = &Foo::num_;std::cout << "num_: " << f_num(foo) << '\n';// store a call to a member function and objectusing std::placeholders::_1;std::function<void(int)> f_add_display2 = std::bind(&Foo::print_add, foo, _1);f_add_display2(2);// store a call to a member function and object ptrstd::function<void(int)> f_add_display3 = std::bind(&Foo::print_add, &foo, _1);f_add_display3(3);// store a call to a function objectstd::function<void(int)> f_display_obj = PrintNum();f_display_obj(18); }還有更多的例子可以看下面這個
//代碼出自鏈接:http://www.jellythink.com/archives/771 #include <functional> #include <iostream> using namespace std;std::function< int(int)> Functional;// 普通函數 int TestFunc(int a) {return a; }// Lambda表達式 auto lambda = [](int a)->int{ return a; };// 仿函數(functor) class Functor { public:int operator()(int a){return a;} };// 1.類成員函數 // 2.類靜態函數 class TestClass { public:int ClassMember(int a) { return a; }static int StaticMember(int a) { return a; } };int main() {// 普通函數Functional = TestFunc;int result = Functional(10);cout << "普通函數:"<< result << endl;// Lambda表達式Functional = lambda;result = Functional(20);cout << "Lambda表達式:"<< result << endl;// 仿函數Functor testFunctor;Functional = testFunctor;result = Functional(30);cout << "仿函數:"<< result << endl;// 類成員函數TestClass testObj;Functional = std::bind(&TestClass::ClassMember, testObj, std::placeholders::_1);result = Functional(40);cout << "類成員函數:"<< result << endl;// 類靜態函數Functional = TestClass::StaticMember;result = Functional(50);cout << "類靜態函數:"<< result << endl;return 0; }關于類的成員函數,還可以在類里面這么寫,與this連用:
Application::Application() {m_Window = std::unique_ptr<Window>(Window::Create());m_Window->SetEventCallback(std::bind(&Application::OnEvent, this, std::placeholders::_1)); // 綁定的是本成員的OnEvent函數,留了一個參數輸入(Event& e) }總結
以上是生活随笔為你收集整理的Hazel引擎学习(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: oracle cdc 关闭,Oracle
- 下一篇: npz文件转为npy_numpy的文件存
