web-dev-qa-db-ja.com

追加のインクルードを回避するために、関数ポインターを別のクラスに渡すことは現実的ですか?

私のプログラムでは、メインライブラリに外部ライブラリ(GLFW)のヘッダーを含めました。このライブラリを使用するために必要なものはすべて、メインクラスで処理できますが、別のクラスで必要な2つの卑劣な小さなメソッドは例外です。

#include <GLFW/glfw3.h>
#include "graphics/Display.hpp"

int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
    glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

    GLFWwindow* window = glfwCreateWindow(800, 600, "Adventum", nullptr, nullptr);

    uint32_t extCount;
    const char** extensions = glfwGetRequiredInstanceExtensions(&extCount);

    Display* display = new Display();

    //@formatter:off
    auto terminate = [&](){glfwSetWindowShouldClose(window, true);};
    auto surfaceCreation = [&](VkSurfaceKHR* surface){return (glfwCreateWindowSurface(display->instance, window, nullptr, surface));};
    //@formatter:on

    display->setTerminateFunction(terminate);
    display->setSurfaceCreationFunction(surfaceCreation);
    display->create(extensions, extCount);

    while (!glfwWindowShouldClose(window))
    {
        glfwPollEvents();
    }

    display->destroy();
    delete display;

    glfwDestroyWindow(window);
    glfwTerminate();
}

これは私のmain関数です。私が必要とする2つの関数はglfwSetWindowShouldCloseglfwCreateWindowSurfaceで、どちらもmainの変数への参照が必要であり、両方のクラスにヘッダーを含めることに加えて追加のハードルになります。ご覧のとおり、外部関数呼び出しを含む2つのラムダ関数(terminatesurfaceCreation)を作成することでこれを解決しました。

私の質問は、これは経験豊富な開発者にとって目障りなものになるのでしょうか?これは粗雑で不要な回避策ですか? (私はそれが「意見に基づく」ことなしにこれを尋ねる方法を理解しようとしています。)

6
小奥利奥

序文。この投稿の多くは、一般的に「C++ラムダをクロージャーとして使用して依存性と結合を減らす」ためのものです。したがって、そのようなコード全般に関するアドバイスが含まれています。ここのサンプルコードは、ここで与えられたすべてのアドバイスを満たしているようです。


この場合、C++ラムダをクロージャーとして使用することはまったく問題ありません。カップリングとヘッダーの依存関係を減らすため、これもお勧めします。

次の注意事項があります。これらは「知識に関する警告」です。つまり、最初に習得する必要があります。その後、恐れることなくC++ラムダを使用できます。

  1. C++コピーと参照の深い理解。
  2. C++のスコープと寿命の深い理解。
  3. (1)と(2)(コピー、参照、スコープ、寿命)がC++ラムダにどのように適用されるかを理解します:キャプチャされた変数とラムダ自体。
  4. 正しいライフタイム特性を持つC++ラムダを作成できます。
  5. 入力シグニチャーと指定子を正しく使用することにより、意図した存続期間特性を持つC++ラムダ入力引数を適切に受け取る関数とメソッドを作成できます。通常、委任先はC++ラムダを_std::function_として受け取ります(そして、おそらくそれを格納します)。

1つのメンテナンスの警告。

  1. C++ラムダ(クロージャ)を使用すると、外部コードが内部の内容を知らなくてもタスクが実行されるようにスケジュールできますが、「デリゲータ」(C++ラムダをインスタンス化して提供するコード)と「デリゲート」の間の相互理解が必要です。 (C++ラムダを受け取り、後で実行するコード)-他のコードとの関連で、実行の順序について。

実行順序の依存関係に関する経験則:

  1. 通常、設定を行うためのコードの順序は簡単です。依存関係は自然で明示的です。 (後の手順では、前の手順で作成した値とオブジェクトが必要になる可能性があります)

  2. ただし、物を降ろす(破壊する)コードの順序は簡単ではありません。多くの場合、事態はプログラマーが意図した順序とは異なる順序で発生します。

    • ヒント:破壊の順序が正確さにとって重要である場合は、ロギングを使用して、手順が正しい順序で行われるようにします。

これらの知識をコードサンプルに適用します。

  • まず、落ち着いてください。これはint main()メソッドであることを忘れないでください。したがって、このメソッドで作成されるほとんどのローカル変数は、アプリケーションのランタイムである限り、実際には最後にalmostになります。ただし、mainメソッドの終了後に実行されるコード(静的オブジェクトのデストラクタなど)がある場合は、解放後使用の条件がトリガーされる可能性があります。

  • 最初のラムダ(terminate)では、ローカル変数window参照によってキャプチャされますであることを理解してください。 windowはポインターですが、参照によるキャプチャ([&](){...})を使用すると、このポインターを格納するローカル変数のアドレスがキャプチャされます。

  • ポインター値をコピーする場合は、copy-by-copy([=](){...})を使用するか、変数を名前で指定します([window](){...})。

  • ポインター値を保持する変数がデリゲートによってアクセスされることを意図している場合(デリゲートが別のオブジェクトを指すように更新する必要がない限り、ほとんどありません)、capture-by-referenceを使用します。

  • キャプチャーされるオブジェクトの存続期間を延長する必要がある場合は、C++スマートポインターのいずれかを使用します。通常は_shared_ptr_または_weak_ptr_;めったに_unique_ptr_(注を参照)。これは、委任者が委任者よりも長く生きることを意図している場合に役立ちます。 (mainメソッドは、このサンプルコードのどのデリゲートよりも長く存続するため、ここではそうではありません。)

  • _unique_ptr_のキャプチャに関する注意。 _unique_ptr_を委任者からラムダ(「タスク/クロージャ」)に適切に転送するには、C++ 14が必要です。これはlambdaキャプチャ式を使用します。 (C++ 11ではなくC++ 14を自由に選択できないほとんどの人は、ほとんどの場合_shared_ptr_または_weak_ptr_にとどまることで問題を回避します。)

5
rwong