1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[USD/C++] CMake USD 用語集

Last updated at Posted at 2024-12-14

用語集

こちらは [USD/C++] ArPathmapResolverを作ろう [Github] の記事の補助として用語の説明をまとめた記事になります
CMakeやUSDでのマクロや、オプションについてできるだけ説明しています

Cmake

Environment

CMAKE_GENERATOR

プロジェクトのビルドに使用するジェネレータを指定

コマンドラインの -G とほぼ同等

// コマンドライン
cmake -G "Visual Studio 16 2019"
cmake -G "Unix Makefiles"
//環境変数の場合
set CMAKE_GENERATOR="Visual Studio 16 2019"
export CMAKE_GENERATOR="Unix Makefiles"

変数の方にも CMAKE_GENERATOR はあるがそちらは 非推奨

Variables

CMAKE_<LANG>_COMPILER

<LANG> 用のコンパイラをフルパスで指定します

set(CMAKE_CXX_COMPILER "/usr/local/bin/gcc")
set(CMAKE_CXX_COMPILER "C:\MinGW\bin\gcc.exe")
set(CMAKE_CXX_COMPILER "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\bin\Hostx64\x64\cl.exe")

CMAKE_BUILD_TYPE

ジェネレーターのビルドタイプを指定します

CMakeLists.txt
set(CMAKE_BUILD_TYPE Debug)  # または Release
BUILD_TYPE
Debug          : 最適化なし、デバッグ情報あり
Release        : 最適化あり、デバッグ情報なし
RelWithDebInfo : 最適化あり、デバッグ情報あり
MinSizeRel     : 最小サイズで最適化、デバッグ情報なし

CMAKE_BUILD_TYPEproject() よりも前に記述する必要があります

CMAKE_VERBOSE_MAKEFILE

ビルドプロセス中に実行されるコマンドを詳細に表示させる変数

set(CMAKE_VERBOSE_MAKEFILE ON)

CMAKE_BINARY_DIR / CMAKE_CURRENT_BINARY_DIR

CMakeでビルドしているディレクトリを返す変数

CMAKE_BINARY_DIR はトップレベルのディレクトのパスを返します

add_subdirectory() によって追加されたディレクトリを取得する場合は CMAKE_CURRENT_BINARY_DIRを使用します

CMAKE_INSTALL_PREFIX

install()コマンドを使用した時のインストールディレクトリを指定

set(CMAKE_INSTALL_PREFIX "/path/to/install")

CMAKE_EXPORT_COMPILE_COMMANDS

コンパイルコマンドを compile_commands.json というファイルにエクスポートするためのオプションです

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

今回はVisual Studio Codeでのコード補完やインテリセンスを使用したいのでONにしています

CMAKE_MESSAGE_LOG_LEVEL

message() コマンドで使用されるロギング・レベルを指定します
STATUS がデフォルトとして設定されています

set(CMAKE_MESSAGE_LOG_LEVEL "DEBUG")
TYPE
ERROR
WARNING
NOTICE
STATUS
VERBOSE
DEBUG
TRACE

CMAKE_CXX_STANDARD

CMakeで使用するC++コンパイラの標準を指定するための変数

# C++17を使用する設定
set(CMAKE_CXX_STANDARD 17)

CMAKE_CXX_STANDARD_REQUIRED

指定した C++ がサポートされていない場合に、ビルドを継続するか、エラーを発生させるかを決定します。
True に設定すると C++ がサポートしていない場合にエラーが発生します

set(CMAKE_CXX_STANDARD_REQUIRED TRUE)

WIN32 / LINUX

OSを識別するための変数
他にもあるので、こちらも確認

LINUX はCMake 3.25から追加されました

if(WIN32)
    # Windows向けの設定
    message("Building on Windows")
elseif(LINUX)
    # Linux向けの設定
    message("Building on Linux")
else()
    # その他のプラットフォーム向けの設定
    message("Building on an unknown platform")
endif()

BUILD_SHARED_LIBS

共有ライブラリ(.dll .soなど)をビルドするか、静的ライブラリ(.aや.libなど)をビルドするかを制御するためのオプション

set(BUILD_SHARED_LIBS ON)

Commnads

cmake_minimum_required

プロジェクトに必要な cmake の最小バージョンを設定します

FATAL_ERROR オプションを使用すると、指定したバージョン未満の場合はビルドを中止します

cmake_minimum_required(VERSION 3.25 FATAL_ERROR)

cmake_minimum_requiredproject() よりも前に記述する必要があります

project

プロジェクトの名前を設定します
プロジェクト名や言語設定を明確にするためにも必ず設定

Option
# LANGUAGES    : 言語を明示的に指定
# VERSION      : プロジェクトにバージョン情報を付ける
# DESCRIPTION  : プロジェクトの簡単な説明を指定
# HOMEPAGE_URL : プロジェクトのホームページやリポジトリのURLを指定

# e.g.
project(Hoge VERSION 1.0.0 LANGUAGE C++)

message

指定されたメッセージテキストをログに表示します

DEBUG などを表示する場合は CMAKE_MESSAGE_LOG_LEVEL を設定します

# STATUS         : 通常の情報を表示
# NOTICE         : STATUSよりも強調したい場合に使用
# VERBOSE        : より詳細な情報を表示するために使用
# DEBUG          : 開発者向けのメッセージを表示
# TRACE          : 細かいメッセージ
# WARNING        : 警告を表示
# AUTHOR_WARNING : 開発者向けの警告メッセージを表示
# SEND_ERROR     : エラーメッセージを表示。処理を続行
# FATAL_ERROR    : エラーメッセージを表示。処理を停止
# DEPRECATED     : 非推奨の機能や方法を使っている場合に警告を表示

message(DEBUG "This is a debug message.")

set

変数に値を設定したり、リストや環境変数なども設定できます

# 通常の変数
set(MY_VAR "Hello!")

# リストの設定
set(MY_LIST "list1" "list2" "list3")

# 環境変数の設定
set(ENV{MY_ENV_VAR} "env")

# 子階層から上位のスコープ(親階層など)に変数を設定
set(MY_VAR "value" PARENT_SCOPE)

# CMakeキャッシュに値を設定する
# これで設定した場合はコマンドラインなどから上書きできる
set(MY_VAR "default_value" CACHE STRING "custom variable")
# cmake -D MY_VAR="new_value"

add_executable

指定されたソースファイルを使ってプロジェクトに実行ファイルを追加するコマンド

add_executable(my_program main.cpp)

add_library

指定されたソースファイルを使ってプロジェクトにライブラリを追加するコマンド
下記オプションの指定ができます

add_library(mylibrary SHARED src/hoge.cpp src/fuga.cpp)
STATIC : 静的ライブラリ(.a や.lib)
SHARED : 動的ライブラリ(.soや.dll)
MODULE : 動的ライブラリに近いが、dlopenのような機能を使ってロードされる事を前提としたもの

add_subdirectory

CMakeでサブディレクトリを追加するためのコマンド

このコマンドを使うことで、サブディレクトリ内で定義された CMakeLists.txt をメインのビルドに組み込むことができます

CmakeLists.txt #1
# hoge
# ├── CMakeLists.txt #1
# └── src
#     └── CMakeLists.txt #2
add_subdirectory("src") #2を#1に組み込む事ができる

add_dependencies

ターゲット間の依存関係を設定するために使用するコマンド
設定をする事で、別のターゲットのビルドが完了するまで自身のビルドを行わないようにする事ができます

add_executable(MyApp main.cpp)
add_library(MyLibrary STATIC my_library.cpp)

# MyApp に MyLibrary をリンク
target_link_libraries(MyApp MyLibrary)

# 依存関係の追加
# MyLibraryに依存しているので、MyAppをビルドする前にMyLibraryが先にビルドされます
add_dependencies(MyApp MyLibrary)

add_compile_options / target_compile_options

CMakeでコンパイラのオプションを追加するためのコマンド

特定のターゲットに対して設定する場合は
target_compile_options を使用する

add_compile_definitions / target_compile_definitions

CMakeでコンパイラのマクロ定義を設定するためのコマンド

特定のターゲットに対して設定する場合は
target_compile_definitions を使用する

link_directories / target_link_directories

CMakeでリンクするライブラリの検索パスを指定するためのコマンド

特定のターゲットに対して設定する場合は
target_link_directories を使用する

link_directories は指定されたディレクトリ内のすべてのライブラリをリンクする可能性があるので 非推奨

link_libraries / target_link_libraries

CMakeでリンクするライブラリを指定するためのコマンド

特定のターゲットに対して設定する場合は
target_link_libraries を使用する

target_link_directories でディレクトリを指定している場合は lib と 拡張子を除いた名前で記述できます、フルパスの場合はそのまま記述

# libhoge.so (target_link_directoriesでディレクトリを指定済み)
# libfuga.so
target_link_libraries(<TARGET>
    PRIVATE
    hoge
    /path/to/lib/libfuga.so
)

link_libraries は全てのターゲットに影響するので 非推奨

include_directories / target_include_directories

CMakeでヘッダーファイルの検索パスを指定するためのコマンド

特定のターゲットに対して設定する場合は
target_include_directories を使用する

include_directories は全てのターゲットに影響するので 非推奨

target_〜の影響範囲

- PRIVATE   : ターゲット内部でのみ有効
- PUBLIC    : ターゲットとそのリンク先のターゲットに有効
- INTERFACE : ターゲット自体には影響しないが、リンクした別のターゲットに影響

set_target_properties

特定のターゲットに対して、プロパティを指定するために使用するコマンド

例えば、ライブラリのプリフィックスを上書きする場合は下記で指定できる

set (MY_LIB "hoge")
add_library(${MY_LIB} SHARED fuga.cpp)
set_target_properties(${MY_LIB} PROPERTIES PREFIX "")
# libhoge.so -> hoge.so

プロパティの種類は他にもあるので、こちらを確認

configure_file

入力ファイルの内容を変換しながら input ファイルを output ファイルにコピーするコマンド

config.json.in
{
    "var1" : "@FOO@",
    "var2" : "@BAR@"
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0)

# 変数のセット
set(FOO "hoge")
set(BAR "fuga" )

# configure_file を使って config.json.in を処理
configure_file(
  ${CMAKE_SOURCE_DIR}/config.json.in # 入力ファイル(テンプレート)
  ${CMAKE_BINARY_DIR}/config.json    # 出力ファイル
  @ONLY                              # 変数の置換を @VAR@ 形式の参照に制限する
)
config.json
{
    "var1" : "hoge",
    "var2" : "fuga"
}

今回は plugInfo.json.inCMakeLists.txt でセットした変数へと置き換えるために使用しています

install

ビルド後に生成されたファイル等を、指定したディレクトリへとインストールするために使用するコマンド

install(TARGETS <target> DESTINATION <dir>)
TARGETS     : ビルドしたターゲットをインストール
FILES       : 単一または複数のファイルをインストール
DERECTORY   : ディレクトリ内のファイルとサブディレクトリを再帰的にインストール
DESTINATION : インストール先のディレクトリパス(絶対パスや相対パス

オプションは他にもあるので、こちらを確認


Definitions

NOMINMAX

windef.hwindows.h では、minmax のマクロが定義されており、
それらのマクロが std::minstd::max というC++の標準ライブラリ関数などと衝突してしまう可能性があるため

#define NOMINMAX を使ってマクロを無効化しています

WIN32_LEAN_AND_MEAN

不要なWindows APIからヘッダーを除外することで、ビルド時間が短縮されます
またC++でUSDのコードを書こう(準備編)でも記載されているように、それらのヘッダーで定義されたマクロとの衝突を避けるためにも

#define WIN32_LEAN_AND_MEAN を定義しています

_GLIBCXX_USE_CXX11_ABI

C++11 ABIを使用するライブラリと古いABIを使用するライブラリ間でバイナリの互換性がなくなるため、古いライブラリと新しいライブラリが混在していると、実行時に問題が発生することもあるので、DCCで使用されているABIにあわせて定義しています

#define _GLIBCXX_USE_CXX11_ABI 1

BOOST_ALL_NO_LIB

Boostの自動リンクの機能を無効にします

これを指定しない場合、Boostはリンクの必要がないヘッダーオンリーライブラリを使う場合でも、ライブラリを自動的にリンクしようとします。

不必要なライブラリのリンクを行わない為にも
#define BOOST_ALL_NO_LIB を定義しています

自動リンクを有効にするためにはコンパイラが #pragma comment(lib, ...) をサポートしている必要があるので、主にMSVCでのコンパイル時に定義します

HBOOST_ALL_NO_LIB

Houdiniで展開されているboostはプリフィックスに h が付くので
Houdiniからboostを使用する場合はマクロも H を付ける必要があります


Compiler Options

/Zc:inline

このオプションを有効にすると、参照されていない変数や関数などのシンボルを
コンパイラは「未使用」と見なし、最適化によって削除します

MSVC 2019 以降、このオプションがデフォルトで有効になっています

なぜ無効(/Zc:inline-)にするのか

attributes.hで定義されている ARCH_CONSTRUCTOR ARCH_DESTRUCTOR というマクロで使われているシンボルが/Zc:inlineによって未使用と判断されてしまうため、
そのシンボルが削除されないように /Zc:inline- で無効にする必要があります

具体的には

attributes.h: #290
#   define ARCH_CONSTRUCTOR(_name, _priority, ...)                             \
    static void _name(__VA_ARGS__);                                            \
    namespace {                                                                \
    __declspec(allocate(".pxrctor"))                                           \
    extern const Arch_ConstructorEntry                                         \
    _ARCH_CAT_NOEXPAND(arch_ctor_, _name) = {                                  \
        reinterpret_cast<Arch_ConstructorEntry::Type>(&_name),                 \
        static_cast<unsigned>(PXR_VERSION),                                    \
        _priority                                                              \
    };                                                                         \
    }                                                                          \
    _ARCH_ENSURE_PER_LIB_INIT(Arch_ConstructorInit, _archCtorInit);            \
    static void _name(__VA_ARGS__)

この部分の namespace{} で囲まれている _ARCH_CAT_NOEXPAND(arch_ctor_, _name) というシンボルが削除されるようです

VS2017では回避できていたみたいですが、VS2019以降ではexternを付けてもエラーになるとの事

/OPT:NOREF

/OPT:NOREF を有効にする事でも、参照されない関数とデータが保持されるので
/Zc:inline- の代わりとして指定する事もできます

/w

コンパイル時に警告メッセージを表示しないようにする

-fPIC

動的リンクライブラリ(.so)を作成する際に主に使用されるオプション

-Wno-deprecated

非推奨機能の警告を無効にする

-Wno-deprecated-declarations

非推奨宣言の警告を無効にする

-Wno-changes-meaning

意味が変わる可能性のある変更に関する警告を無効にする


USD

Macros

PXR_NAMESPACE_OPEN_SCOPE / PXR_NAMESPACE_CLOSE_SCOPE

名前空間を開閉するためのマクロです。ビルドによって変わりますが、pxr.h では
pxrInternal_v0_24_11__pxrReserved__ という名前空間で展開されています

pxr.h #22
#define PXR_NS pxr
#define PXR_INTERNAL_NS pxrInternal_v0_24_11__pxrReserved__
#define PXR_NS_GLOBAL ::PXR_NS
 
namespace PXR_INTERNAL_NS { }
 
// The root level namespace for all source in the USD distribution.
namespace PXR_NS {
    using namespace PXR_INTERNAL_NS;
}
 
#define PXR_NAMESPACE_OPEN_SCOPE   namespace PXR_INTERNAL_NS {
#define PXR_NAMESPACE_CLOSE_SCOPE  }  
#define PXR_NAMESPACE_USING_DIRECTIVE using namespace PXR_NS;

のように定義されており PXR_INTERNAL_NS
PXR_NS の名前空間ではusing ディレクティブが使用されています

PXR_NAMESPACE_USING_DIRECTIVE

using namespace PXR_NS というディレクティブを挿入します

これによって名前空間のメンバーをコード内で簡単に使用する事ができます

#include <pxr/base/tf/token.h>

PXR_NS::TfToken("hoge");
#include <pxr/base/tf/token.h>

PXR_NAMESPACE_USING_DIRECTIVE

TfToken("hoge");

AR_API / AR_LOCAL / ARCH_IMPORT / ARCH_EXPORT / ARCH_HIDDEN

ライブラリのシンボルをエクスポートしたり、インポートする時の挙動を制御するためのマクロ

具体的にはコンパイラやOSに応じて

MSVC
__declspec(dllexport) // Export
__declspec(dllimport) // Import
GCC(4+)/Clang
// Windows
__attribute__((dllexport)) // Export
__attribute__((dllimport)) // Import
// Unix
__attribute__((visibility("default"))) //Export
__attribute__((visibility("hidden")))  //Hide

などのアトリビュートを設定します

Windowsではシンボルのエクスポートとインポートを明示的に記述する必要があり、Unix系では可視性でシンボルを管理するので、外部から使用されたくない場合は __attribute__((visibility("hidden"))) を設定します

AR_DEFINE_RESOLVER(ResolverClass, BaseClass...)

指定したリゾルバーを登録するためのマクロ

第一引数に登録するリゾルバー、それ以降の引数に基底クラスを指定します

defineResolver.hをみてみると、
TF_REGISTRY_FUNCTION(TfType) マクロ内で、TfType::Define を使って TfType を定義、その後 SetFactory でリゾルバーを登録しているみたいです

  • ソース内で使う事が想定されています
  • pluginfo.json の基底クラスも合わせないと、読み込み時にエラーとなる可能性があります

AR_DECLARE_RESOLVER_CONTEXT(ContextObject)

指定された ContextObjectArResolverContext のコンテキストオブジェクトとして使用できることを宣言するマクロ

マクロが展開されるとArIsContextObject<ContextObject>の構造体が定義され、コンテキストとして使用できることを示すために value メンバを true に設定しています

この構造体はResolverの変更を通知するための ArNotice クラスで使用されています

ArIsContextObject<ContextObject>::value //true

ContextObject が宣言されているヘッダー内で使用します

TF_WRAP(x)

Boost.Pythonライブラリを使用して、C++のクラスや関数を
Pythonで使用できるようにラップするための関数を宣言、実行するためのマクロ

例えば TF_WRAP(Hoge) とした場合、下記のように展開されます

ARCH_HIDDEN void wrapHoge();
wrapHoge()

wrap〜という関数の実装は別途行う必要があります

TF_WRAP_MODULE

TF_WRAP で展開された関数を処理するための WrapModule() という関数を定義します。TF_WRAP は基本的にこのマクロ内で使用します

具体的には

TF_WRAP_MODULE
{
    TF_WRAP(Hoge);
    TF_WRAP(Fuga);
}

というマクロがこのように展開されます

static void WrapModule()
{
    ARCH_HIDDEN void wrapHoge(); wrapHoge();
    ARCH_HIDDEN void wrapFuga(); wrapFuga();
}

そしてWrapModule関数はPyInit_<module_name>という
Boost.Pythonで拡張モジュールを初期化する際に使用される関数内で呼び出されています

TF_DEBUG(enumVal)

enumVal が有効かどうかを確認するためのマクロ

有効であれば TF_DEBUG(enumVal).Msg(...) でデバッグメッセージを表示する事ができます。
また、enumValTF_DEBUG_CODES() のマクロで定義されている必要があります

if (!TfDebug::IsEnabled(enumVal)) /* empty */ ; else TfDebug::Helper()

展開されると上記の式が評価され、enumVal が有効であれば TfDebug::Helper という構造体を返します

TF_DEBUG_CODES(...)

TF_DEBUG で使用するための、デバッグシンボルを定義するためのマクロ

このマクロを使用する事でデバッグシンボルを列挙した enum 型を定義したり、
デバッグシンボルに関する構造体や関数などが定義されます。

TF_DEBUG_CODES(
  MY_E1,
  MY_E2
);

というマクロがこのように展開されます

// 列挙型の名前は 最初の引数に__DebugCodesを付けた名前が定義されます
enum MY_E1__DebugCodes {
    MY_E1,
    MY_E2,
    MY_E1__DebugCodes__PAST_END //内部的に使用される定数
};

// デバッグコードの情報を格納した構造体
template <>
struct TfDebug::_Traits<MY_E1__DebugCodes> {
    static constexpr bool IsDeclared = true;
    static constexpr int NumCodes = MY_E1__DebugCodes__PAST_END;
    static constexpr bool CompileTimeEnabled = true;
};

//
inline char const *
Tf_DebugGetEnumName(MY_E1__DebugCodes val) {
    constexpr char const *CStrings[] = {
        "MY_E1",
        "MY_E2",
        "MY_E1__DebugCodes__PAST_END"
    };
    return CStrings[static_cast<int>(val)];
};

TF_DEBUG環境変数に TF_DEBUG_REGISTRY を設定することで挙動を確認できます

TF_DEBUG_ENVIRONMENT_SYMBOL(VAL, descrip)

デバッグシンボルにデバッグ内容の説明文を指定することができるマクロ
このマクロを定義しなくても、TF_DEBUGでのデバッグ自体は有効

というのも、TF_DEBUG_ENVIRONMENT_SYMBOL では Tf_DebugSymbolRegistry を使ってデバッグシンボルを登録しますが、

TF_DEBUGの環境変数が設定されていたり、TfDebug::SetDebugSymbolsByName などの関数を実行したタイミングでも初期化されるため、TF_DEBUG_ENVIRONMENT_SYMBOL で登録しなくても TF_DEBUG 自体は効きます

  • ヘッダーではなく、ソースファイル内の TF_REGISTRY_FUNCTION(TfDebug) マクロの中で使うことが推奨されています
  • 登録するデバッグシンボルは、TF_DEBUG_CODES() のマクロで定義されている必要があります
debugCodes.cpp
TF_REGISTRY_FUNCTION(TfDebug) {
    TF_DEBUG_ENVIRONMENT_SYMBOL(MY_E1, "loading of blah-blah files");
    TF_DEBUG_ENVIRONMENT_SYMBOL(MY_E2, "parsing of mdl code");
    // etc.
}

TF_REGISTRY_FUNCTION(KEY_TYPE)

レジストリ(TfDebugなどの型)に関数を登録するためのマクロ
登録した関数を呼び出す時はTfRegistryManager::SubscribeTo<KEY_TYPE>()を使います

これによって、SubscribeTo<KEY_TYPE>()で指定された型の関数のみが実行されるので、不要な呼び出しを避ける事ができます

// 登録
TF_REGISTRY_FUNCTION(KEY_TYPE)
{
    //ここに処理を入れる
}

// 呼び出し
TfRegistryManager::GetInstance().SubscribeTo<KEY_TYPE>()

TF_DEBUG 環境変数に TF_DISCOVERY_TERSE を設定することで挙動を確認できます

KEY_TYPEはテンプレート化や、名前空間で修飾されていない必要があります

// ダメな例 : テンプレート
template <typename T>
class Hoge {};
TF_REGISTRY_FUNCTION(Hoge<int>){}
// ダメな例 : Namespace
namespace Hoge {
    class Fuga {};
}
TF_REGISTRY_FUNCTION(Hoge::Fuga){}

もし名前空間を使用している場合は、using namespace を使って名前空間を指定した後、TF_REGISTRY_FUNCTION を使用します

TF_PP_STRINGIZE(x)

x を文字列に変換します

例えば TfToken のコンストラクタでマクロを使いたい場合、TF_PP_STRINGIZE を使用して文字列へと変換する事ができます

#define HOGE fuga

TfToken(TF_PP_STRINGIZE(HOGE)); // TfToken("fuga")

MFB_PACKAGE_NAME / MFB_ALT_PACKAGE_NAME / MFB_PACKAGE_MODULE

USDで内部的に使われているマクロ。

Cmakeの pxr_plugin(NAME) 関数からビルドする場合は
PXR_PACKAGE という変数を設定するだけでよい

手動で設定する場合は、
MFB_PACKAGE_NAME MFB_ALT_PACKAGE_NAME はライブラリの名前、
MFB_PACKAGE_MODULEMFB_PACKAGE_NAME の頭文字を大文字にした名前を設定

主に下記で使われています

  • TfAutoMallocTag2 でメモリを管理する時のタグ
  • TfRegistryManager でライブラリのコンストラクター、デストラクタを実行
  • Pythonでモジュールを作成する時の名前

Classes

TfRegistryManager

プログラム起動時にライブラリが自動的に初期化される仕組みを、効率的に管理するためのクラス

ライブラリの初期化を必要な時に呼び出せるので、
スレッドセーフに処理したり、不要な初期化を避ける事ができます

静的コンストラクタ

プログラム起動時に初期化する方法として、
静的メソッドや静的変数がプログラム開始時に初期化されるという特性を活かして、
静的コンストラクタに近い動作をさせるテクニックがあります。

main.cpp
#include <iostream>

class MyClass {
public:
    static void initialize() {
        std::cout << "initialize!\n";
    }
};

struct Initializer {
    Initializer() { MyClass::initialize(); }
};

static Initializer forceInit;

int main() {
    std::cout << "main!\n";
}
結果
initialize!
main!

静的変数 forceInit のインスタンスが作成されると、Initializer のコンストラクタが呼び出され、その中で MyClass::initialize() が実行されます

このように書く事で静的コンストラクタに近い動作を実現することができました

TfRegistryManagerによる拡張

ただ、そのまま使用するといくつか問題があるので、TfRegistryManager によってそれらの問題を解決しています

  • 各OSで初期化のタイミングが不確定
  • 複数のライブラリがあった時の依存関係の管理が難しい
  • 無駄な初期化が発生する
  • スレッドセーフではない可能性がある

基本的には上記の静的コンストラクタやコンパイラで用意されている特別なアトリビュートを使用していますが、
※GCCやClangなら__attribute__((used, constructor))のような

そこではまず関数の登録のみを行います。その後、必要になったタイミングでTfRegistryManager::SubscribeTo<KEY_TYPE>()を呼び出す事で、任意のタイミングでの関数を実行する事ができます

TfScriptModuleLoader

Pythonで利用できるようにバインディングされたライブラリの
読み込みや依存関係の管理などを適切に行うクラス

主に以下のタイミングでモジュールが読み込まれます

  • スクリプトモジュールがロードされた時
  • TfPyInitializeが呼ばれた時
  • Plugが共有ライブラリを開いた時

TF_DEBUG 環境変数に TF_SCRIPT_MODULE_LOADER を設定することで挙動を確認できます

RegisterLibrary(name, moduleName, predecessors)

ライブラリをTfScriptModuleLoaderに登録するための関数

name には C++の動的ライブラリの名前
moduleName にはPythonのモジュール名
predecessors は依存するライブラリの動的配列

をそれぞれTfToken型で指定します

ar/moduleDeps.cpp
TF_REGISTRY_FUNCTION(TfScriptModuleLoader) {
    // List of direct dependencies for this library.
    const std::vector<TfToken> reqs = {
        TfToken("arch"),
        TfToken("js"),
        TfToken("plug"),
        TfToken("tf"),
        TfToken("vt")
    };
    TfScriptModuleLoader::GetInstance().
        RegisterLibrary(TfToken("ar"), TfToken("pxr.Ar"), reqs);
}

PlugRegistry

C++の共有ライブラリや、Pythonモジュールなどで作成されたプラグインに対して登録や読み込みを動的に行い、それらを管理するためのクラス

プラグインを登録するにはplugInfo.jsonを用意し、
RegisterPlugins()という関数を使用します

登録されたプラグインは、プラグインが必要とされるまで読み込まれないので、
効率的にリソースを管理する事ができます

plugInfo.json

plugInfo.json はプラグインの登録に必要な情報を記述するためのファイルです
PlugRegistry は、このファイルをもとにプラグインを読み込み、依存関係を解決します

/ar/resources/plugInfo.json
{
    "Plugins": [
        {
            "Info": {
                "Types": {
                    "ArResolver": {},
                    "ArDefaultResolver": {
                        "bases": [
                            "ArResolver"
                        ],
                        "implementsContexts": true
                    },
                    "ArPackageResolver": {}
                }
            },
            "LibraryPath": "../../libpxr_ar.so", 
            "Name": "ar", 
            "ResourcePath": "resources", 
            "Root": "..", 
            "Type": "library"
        }
    ]
}
ライブラリの動的な読み込み

動的ライブラリの呼び出し、終了は TfDlopen TfDlclose を使用しています

具体的にはOSに応じて、下記関数が呼び出されています

Windows
LoadLibrary() // Open
FreeLibrary() // Close
Unix
dlopen()  // Open
dlclose() // Close

TfStaticData

グローバル変数や静的メンバー変数などの、グローバルデータを
安全に管理する為のクラステンプレート

TfStaticData はローカル変数では使用できません
グローバルスコープや、無名名前空間のようなファイルスコープで使用する事が推奨されています

グローバルデータの問題点

グローバル変数や静的メンバー変数はプログラム全体で共有する事ができるが、
それらの変数が初期化される前にアクセスされてしまうと、未定義動作を引き起こす可能性があります、

#include <iostream>

int A = B; // Bがまだ初期化されていない状態でAを使おうとする
int B = 10;

int main() {
    std::cout << A << std::endl;
}

また、複数のスレッドからグローバルデータにアクセスしようとすると、競合によってデータの整合性が保たれなくなってしまい、スレッドセーフではなくなるという問題も起こります

#include <iostream>
#include <thread>

int globalVar = 0;

void increment() {
    for (int i = 0; i < 1000; ++i) {
        globalVar++; // 複数スレッドで同時にアクセスすると競合が起こる
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);

    t1.join();
    t2.join();

    std::cout << globalVar << std::endl; // 結果は予測できない(競合による不整合)
}

TfStaticDataによる改善

TfStaticData はデータの初期化を遅延させる事でそれらの問題を回避しています

TfStaticData が指し示すデータはプログラムが実行された段階では初期化されずに、そのデータへ初めてアクセスした時に初期化されます。
そうする事で限りなく安全に初期化することができ、上記の問題を回避することができます

static TfStaticData<std::set<std::string>> Xyz_nameSet;

void XyzAddName(std::string name) {
    Xyz_nameSet->insert(name);  // 初めてアクセスした時に遅延初期化される
}

TfNotice

通知に関するシステムを構成したり、管理するためのクラス

このクラスを継承する事で、
イベントが発生した事をリスナーに知らせる為のセンダーとしての役割も持ちます

リスナーは主に TfWeakBaseTfRefBase を継承したクラスで定義され、
それらが、通知を受け取る役割を持ちます

#include <iostream>

#include <pxr/base/tf/type.h>
#include <pxr/base/tf/notice.h>
#include <pxr/base/tf/weakBase.h>

PXR_NAMESPACE_USING_DIRECTIVE

// Noticeを作成
class TestNotice : public TfNotice
{
public:
    TestNotice() = default;
};

// Listenerを作成
class TestListener : public TfWeakBase
{
public:
    TestListener()
    {
        TfWeakPtr<TestListener> me(this);
        TfNotice::Register(me, &TestListener::ProcessHoge); //登録
    };

    // Register時に引数の型を見ているので、対象のNoticeクラスを引数にする
    void ProcessHoge(const TestNotice &n)
    {
        std::cout << "Hello Notice!" << std::endl;
    };
};

// Noticeを使用するには、一度定義する必要がある
TF_REGISTRY_FUNCTION(TfType)
{
    TfType::Define<TestNotice, TfType::Bases<TfNotice> >();
}

int main() {
    // ListenerのコンストラクタでTestNoticeを登録
    TestListener *l1 = new TestListener();
    // TestNoticeからイベントをSend
    TestNotice().Send();
    return 0;
}
TfWeakBase

TfWeakPtr を使用するための基底クラス
オブジェクトが削除されたかどうかを監視する事ができます

また、継承する事で TfRefPtr 変数が追加され、
TfWeakPtr は、TfRefPtr が管理しているオブジェクトの状態を監視し、
TfRefPtr は実際にオブジェクトを所有し、オブジェクトが有効かどうかを管理します

TfWeakPtr

オブジェクトが削除されたかどうかを監視するためのポインタ
基本的には TfWeakBase を継承したクラスで使用します

参照しているオブジェクトが削除されると TfWeakPtr は自動的に無効化され nullptr になります

また、循環参照が起こらないように、オブジェクトに対する所有権は持ちません

TfRefBase

TfRefPtr を使用するための基底クラス
オブジェクトが実際に参照されているかどうかを管理します

TfRefBase を継承することで、
クラスは参照カウントを管理できるようになります。

TfRefPtr

オブジェクトの所有権を管理するポインタ
TfRefBase を継承したオブジェクトの参照カウントを実装しています

オブジェクトへの最後の参照が破棄されるとオブジェクトを自動的に削除します

Functions

ArWrapResolverContextForPython(context)

PythonからC++の ArResolverContext オブジェクトに変換できるコンテキストオブジェクトとして、指定された型を登録する関数

ArWrapResolverContextForPython<This>();

TfGetBaseName(fileName)

ファイル名を取得する関数

TfGetBaseName("hoge/fuga/fugo.txt") // fugo.txt

TfGetPathName(fileName)

ディレクトリパスを取得する関数
最後にディレクトリ区切りのスラッシュがつきます
※ Windowsの場合は \

TfGetPathName("hoge/fuga/fugo.txt") // hoge/fuga/

TfStringTokenize(source, delimiters)

与えられた文字列を区切り文字で分解し、文字列のベクトルを返します

TfStringTokenize("hoge:fuga:fugo",":")
//std::vector<std::string> {"hoge","fuga","fugo"}

TfStringCatPaths(prefix, suffix)

ファイルパスなどで使われる ../ を考慮した上で、2つの文字列を結合します

TfStringCatPaths( "foo/bar", "jive" )    => "foo/bar/jive"
TfStringCatPaths( "foo/bar", "../jive" ) => "foo/jive"

TfAbsPath(path)

指定されたファイルパスを絶対パスに変換するための関数

  • すでに絶対パスが指定されている場合はそのままそのパスが返されます
  • 相対パスの場合、現在の作業ディレクトリを基準にして絶対パスを返します
  • 指定されたパスが実際に存在しない場合でも、絶対パスを返します

TfIsRelativePath(path)

指定されたファイルパスが相対パスかどうかを確認するための関数

Windows
WindowsAPIのPathIsRelativeW関数の戻り値がTrueかつ、
パスの先頭文字が / や \\ ではない場合にTrue
Unix
パスが空、もしくは先頭文字が / ではない場合にTrue

TfPathExists(path, resolveSymlinks = False)

パスが存在するかどうかを確認するための関数
resolveSymlinksTrue の場合はシンボリックリンクを解決した上で確認します

TfGetenv(envName, defaultValue = "")

環境変数を文字列で返します、見つからない場合は defaultValue が返されます

1
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?