虚幻引擎调用这些宏...这在C++中正常吗?

9

我曾经是一名C++程序员,对我来说,宏是使用#define进行预处理定义的。

现在我开始使用Unreal引擎进行编程,它使用C++,但有所有这些UCLASS()UFUNCTION()FORCELINE,Unreal教程称之为宏。我以前从未见过这样的东西,想要理解它。

我不是在询问Unreal中的宏是做什么的,而是希望有人帮助我填补C++方面的知识缺口,以便我(作为开发人员)能够理解如何设计和何时实现这种类型的宏。即使给我一个指南或链接也可以。我尝试使用术语macro、C++、UCLass、Unreal进行搜索,但这些术语并没有真正提供宏的C++定义。

#include "GameFramework/Actor.h"
#include "Pickup.generated.h"

UCLASS()
class BATTERYCOLLECTOR_API APickup : public AActor
{
    GENERATED_BODY()

public: 
    // Sets default values for this actor's properties
    APickup();

    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

7
你的问题是什么?为什么你认为这些宏与你所知道的宏不同?你认为为什么它们没有用#define定义? - Vincent Savard
2
你是否已经搜索引擎代码中的宏,并查看其定义方式? - Thomas Matthews
这些绝对是宏。它们不符合语言语法中的其它任何部分。 - Kyle Strand
宏是用于文本替换的,不仅仅是#ifndef标志。 - Jarod42
2个回答

20

Unreal引擎的C++代码库使用自定义预处理器,称为 Unreal Header Tool (UHT) ,从您的C++代码生成自定义运行时类型信息(RTTI)。它通过查找代码中的这些特殊类似于宏的注释来实现。严格来说,它们不是简单的C++宏,而是更多的东西。

我不是Unreal Engine的用户,所以我不了解具体实现细节。我不知道UHT预处理器在运行后是否会将它们剥离掉,或者它们只是被定义为空值供C++编译器使用,例如:

#if !RUNNING_UHT
    #define UCLASS()
#endif

这两种方法似乎都是有效的。

这是正常的C++吗?

这要看情况。这显然不是标准的C++。在C++中,宏用得较少,因为语言通常提供更好的选项(例如:内联函数、枚举、const、模板),所以您可能在大多数代码库中看不到那种宏使用。


5
谢谢,我接受你的回答,因为你具体回答了我提出的问题,我可以查找UHT以了解更多信息。对于其他看到这个答案的人,@mykola的答案也非常好,它真正填补了我对宏的使用以及如何实现UHT、UBT的知识缺口。 - Phil
请注意,尽管存在其他语言特性,但宏确实经常在实际中使用。此外,请注意,这听起来与Qt的moc工具非常相似--但请注意,Qt宏确实被用作宏,即最终将它们传递给C++预处理器,而不是被moc工具删除。 - Kyle Strand

11

正如Unreal Engine文档所述:

类声明定义了类的名称、它继承自哪个类以及因此继承的任何函数和变量,以及通过类特定符号和元数据实现的其他引擎和编辑器特定行为。声明类的语法如下:

UCLASS([specifier, specifier, ...], [meta(key=value, key=value, ...)])
class ClassName : ParentName
{
    GENERATED_UCLASS_BODY()
}

声明包含一个标准的C++类声明。在标准声明之上,通过UCLASS宏传递类描述符,例如类说明符和元数据。这些用于创建被声明的类的UClass,可以将其视为引擎对该类的专门表示。还必须在类主体的开头放置GENERATED_UCLASS_BODY()宏。

此宏是用于向引擎类添加一些元数据功能,例如RTTI、反射等等... 这个宏不是由C++ STD库提供的标准宏,而是由Epic Games编写的,以适应虚幻引擎的需求。

有关虚幻引擎Gameplay Classes及其使用的宏的更多信息,请参见这里

下面是虚幻引擎为简单游戏类生成的一些宏代码示例:

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
/*===========================================================================
    C++ class header boilerplate exported from UnrealHeaderTool.
    This is automatically generated by the tools.
    DO NOT modify this manually! Edit the corresponding .h files instead!
===========================================================================*/

#include "ObjectBase.h"

#ifdef MYPROJECTCODE_MyProjectCodeGameMode_generated_h
#error "MyProjectCodeGameMode.generated.h already included, missing '#pragma once' in MyProjectCodeGameMode.h"
#endif
#define MYPROJECTCODE_MyProjectCodeGameMode_generated_h

#define AMyProjectCodeGameMode_EVENTPARMS
#define AMyProjectCodeGameMode_RPC_WRAPPERS
#define AMyProjectCodeGameMode_RPC_WRAPPERS_NO_PURE_DECLS \
    static inline void StaticChecks_Implementation_Validate() \
    { \
    }


#define AMyProjectCodeGameMode_CALLBACK_WRAPPERS
#define AMyProjectCodeGameMode_INCLASS_NO_PURE_DECLS \
    private: \
    static void StaticRegisterNativesAMyProjectCodeGameMode(); \
    friend MYPROJECTCODE_API class UClass* Z_Construct_UClass_AMyProjectCodeGameMode(); \
    public: \
    DECLARE_CLASS(AMyProjectCodeGameMode, AGameMode, COMPILED_IN_FLAGS(0 | CLASS_Transient | CLASS_Config), 0, MyProjectCode, MYPROJECTCODE_API) \
    DECLARE_SERIALIZER(AMyProjectCodeGameMode) \
    /** Indicates whether the class is compiled into the engine */    enum {IsIntrinsic=COMPILED_IN_INTRINSIC}; \
    UObject* _getUObject() const { return const_cast<AMyProjectCodeGameMode*>(this); }


#define AMyProjectCodeGameMode_INCLASS \
    private: \
    static void StaticRegisterNativesAMyProjectCodeGameMode(); \
    friend MYPROJECTCODE_API class UClass* Z_Construct_UClass_AMyProjectCodeGameMode(); \
    public: \
    DECLARE_CLASS(AMyProjectCodeGameMode, AGameMode, COMPILED_IN_FLAGS(0 | CLASS_Transient | CLASS_Config), 0, MyProjectCode, MYPROJECTCODE_API) \
    DECLARE_SERIALIZER(AMyProjectCodeGameMode) \
    /** Indicates whether the class is compiled into the engine */    enum {IsIntrinsic=COMPILED_IN_INTRINSIC}; \
    UObject* _getUObject() const { return const_cast<AMyProjectCodeGameMode*>(this); }


#define AMyProjectCodeGameMode_STANDARD_CONSTRUCTORS \
    /** Standard constructor, called after all reflected properties have been initialized */ \
    MYPROJECTCODE_API AMyProjectCodeGameMode(const FObjectInitializer& ObjectInitializer); \
    DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(AMyProjectCodeGameMode) \
private: \
    /** Private copy-constructor, should never be used */ \
    MYPROJECTCODE_API AMyProjectCodeGameMode(const AMyProjectCodeGameMode& InCopy); \
public:


#define AMyProjectCodeGameMode_ENHANCED_CONSTRUCTORS \
private: \
    /** Private copy-constructor, should never be used */ \
    MYPROJECTCODE_API AMyProjectCodeGameMode(const AMyProjectCodeGameMode& InCopy); \
public: \
    DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(AMyProjectCodeGameMode)


#undef UCLASS_CURRENT_FILE_NAME
#define UCLASS_CURRENT_FILE_NAME AMyProjectCodeGameMode


#undef UCLASS
#undef UINTERFACE
#if UE_BUILD_DOCS
#define UCLASS(...)
#else
#define UCLASS(...) \
AMyProjectCodeGameMode_EVENTPARMS
#endif


#undef GENERATED_UCLASS_BODY
#undef GENERATED_BODY
#undef GENERATED_IINTERFACE_BODY
#define GENERATED_UCLASS_BODY() \
PRAGMA_DISABLE_DEPRECATION_WARNINGS \
public: \
    AMyProjectCodeGameMode_RPC_WRAPPERS \
    AMyProjectCodeGameMode_CALLBACK_WRAPPERS \
    AMyProjectCodeGameMode_INCLASS \
    AMyProjectCodeGameMode_STANDARD_CONSTRUCTORS \
public: \
PRAGMA_POP


#define GENERATED_BODY() \
PRAGMA_DISABLE_DEPRECATION_WARNINGS \
public: \
    AMyProjectCodeGameMode_RPC_WRAPPERS_NO_PURE_DECLS \
    AMyProjectCodeGameMode_CALLBACK_WRAPPERS \
    AMyProjectCodeGameMode_INCLASS_NO_PURE_DECLS \
    AMyProjectCodeGameMode_ENHANCED_CONSTRUCTORS \
private: \
PRAGMA_POP

它的类别。

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "GameFramework/GameMode.h"
#include "MyProjectCodeGameMode.generated.h"

    UCLASS(minimalapi)
    class AMyProjectCodeGameMode : public AGameMode
    {
        GENERATED_BODY()

    public:
        AMyProjectCodeGameMode(const FObjectInitializer& ObjectInitializer);
    };

正如您所看到的,此宏添加了额外的字段变量和方法来提供接口以获取虚幻引擎元数据信息。因此,引擎可以使用此信息来构建关卡、生成玩家等。

虚幻引擎定义的宏不能被重写,除非您想设计自己的引擎框架以添加一些新功能(使用新的宏定义),或修改当前的虚幻引擎以适应您的特定需求(这是非常艰苦的工作)。


1
标准并未提及虚幻宏。 - Lightness Races in Orbit
@Lightness Races in Orbit:谢谢,我忽略了这个。 - Mykola
2
谢谢,这非常有帮助!我忘记了很多关于C++和宏的知识,但是你用一种方式解释了它,使我恢复了记忆,并解释了虚幻引擎使用宏的原因和方式。 - Phil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接