簡體   English   中英

如何在私有成員的構造函數之間調用函數?

[英]How can I call a function “between” the constructors of private members?

我正在編寫一個類來管理OpenGL應用程序的GLEW和GLFW。 框架需要按特定順序初始化。 正確的順序是這樣的:

#include <GL/glew.h>
#include <GLFW/glfw3.h>

glfwInit();
GLFWwindow* w = glfwCreateWindow(1280, 720, "main", 0, 0);
glfwMakeContextCurrent(w);
glewInit();
// GL API is now available

我的類在其構造函數中初始化框架:

class Engine
{
    struct GLEW { GLEW() { glewInit(); } };
    struct GLFW { GLFW() { glfwInit(); } };

    GLFW m_glfw;
    // Window::Window calls glfwCreateWindow and glfwMakeContextCurrent 
    Window m_window; 
    GLEW m_glew;
    // Program::Program requires GL API
    Program m_program; 

public:
    // a simplified version of the real ctor
    Engine(int width, int height, const char* name)
        : m_window(width, height, name)
    {
        m_window.setWindowUserPointer(this);
    }
};

我不得不求助於GLEWGLFW偽結構來在正確的時間調用gl*wInit函數。 這有效,但非常脆弱。 我的程序正確性取決於私人成員的確切順序這一事實讓我擔心。

重要的是glfwInitglewInit在應用程序的整個生命周期中都被調用一次,但我可能會創建WindowProgram類的許多實例。 假設Engine類也將實例化一次。

有一個更好的方法嗎? 我特別希望將框架初始化保留在Engine類中。 同樣地,我不想,比如說,將調用移動到Window::Window glfwInit (這沒有任何意義,因為可能會創建許多窗口,但glfwInit應該只調用一次)。

在C ++中,初始化是構造函數的工作,而清理是析構函數的工作。 對於每個成功的初始化,您通常需要相應的清理操作。 語言機制使其自動化。

使用這種語言提供的支持稱為RAII, 資源獲取是初始化

  • 為要初始化和清理的每種(類型)事物創建一個C ++類。
  • 表示初始化順序,構造函數參數或基類的關系。
  • 對於初始化失敗,拋出異常(以便沒有此類的實例)。

由於OpenGL的東西使用錯誤代碼,你可能會考慮std::system_error而不僅僅是std::runtime_error ; 前者(自C ++ 11起可用)帶有錯誤代碼。

由於glfwInit需要在全局范圍內調用一次,因此我會自動調用該調用。 一種方法是在類的構造函數中放置一個帶初始化的static變量,頭文件聲明該類的虛擬常量。 這確保了跨編譯單元的單個公共初始化。

如果您只想為創建的第一個Window實例執行操作,則可以使用靜態local:

Window::Window()
{
    static auto once = (glfwInit(), true);
    // other stuff
} 

如果你想讓它之前窗口自己的初始化列表中完成的,做一個基類的Window

當您具有隱式順序依賴關系時,您始終可以使用構造函數參數或其他語法約束使它們顯式化。 使你的GLFWGLEW結構適當的類而不是內部的假人。 Window類在構造函數中需要GLFW參數。 讓你的GLEW類在構造函數中需要一個Window參數。 然后你別無選擇,只能確保你有這些東西。

最后,打開編譯器警告,在構造函數中強制執行正確的初始化順序,並使其成為錯誤。 否則,錯誤仍然可以在未檢測到的情況下滑落。

而不是使用您的結構黑客,幾乎總是最好明確初始化您的成員:

...
public:
    // a simplified version of the real ctor
    Engine(int width, int height, const char* name)
        : m_glfw(initGlfw()),
        , m_window(width, height, name),
        , m_glew(initGlew())
    {
        m_window.setWindowUserPointer(this);
    }

...

讓程序依賴於成員對象的順序並不是件壞事,在C ++代碼中很常見。 如果你真的很擔心,你可以啟用警告,如果你做錯了就會觸發(在GCC:-Wreorder)。 它們被聲明的順序是它們應該被初始化的順序。

你有什么不錯,但我理解你的關注....

一種選擇是將窗口初始化推遲到構造函數體,這允許您對其他初始化步驟使用簡單的函數調用。 class Window添加一個其他客戶端代碼無法意外調用的構造函數,這顯然是文檔延遲初始化:

enum Construction { Deferred_Init };
explicit Window::Window(Init);
void init(int width, int height, const char* name);

然后在Engine

Engine(int width, int height, const char* name)
    : m_window(Deferred_Init)
{
    glfwInit();
    m_window.init(width, height, name);
    m_window.setWindowUserPointer(this);
    glewInit();
}

或者, std::unique_ptr<Window>, std :: optional , boost :: optional`都可以用於推遲構造。


另一種選擇是使用類來更明顯地將這些成員對象綁定在一起,因此在代碼維護/演進期間它們不太可能被意外重新排序。 遺憾的是,我在C ++ 11標准中看不到保證 std::tuple<>的構造順序,但你可以很容易地創建類似的東西 - 也許叫做Ordered_Construction來傳達意圖。 這往往會使訪問數據成員變得不那么方便....


您還可以檢查文檔中的初始化未正確完成時返回的錯誤代碼,並確保它們觸發程序以有序且不可忽略的方式關閉,如果初始化被代碼更改破壞。 希望這可以確保在將代碼發布給用戶之前發現任何錯誤....

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM