UEC++与标准C++的区别与联系

Due to the impact of COVID-19, I’ve been confined at home throughout the Spring Festival, and I haven’t returned to work yet. I can only read books and organize my notes, hoping the pandemic ends soon and that my friends stay healthy.
The main content of this article is to analyze the differences in syntax between the C++ used in UE development and standard C++. UEC++ is essentially a superset of C++, supporting and utilizing all features of C++, but it has built its own syntax on top of the standard features. Many compilation issues during development can only be quickly and accurately identified at different stages by understanding the boundaries between the two. For those who learned C++ before using UE, this isn’t a problem, but for students who first encounter UE and then gradually learn C++, this can be quite a significant issue.

Standard C++ is based on the language standard ISO/IEC 14882 (C++98/03/11/14/17, etc.), while UEC++ is Epic’s extended usage built on standard C++. This article will not cover topics like GC or reflection or various libraries in UE, focusing instead on the core syntax level.

In my recent blog updates, I wrote about the UE reflection mechanism, which can be referenced as advanced content in conjunction with this article:

From UE4.23 onward, the C++ standard supported by UE can be controlled. The value of CppStandard can be set in both *.target.cs and *.build.cs. Currently, three standards are available: Cpp14, Cpp17, and Latast, which control the /std:c++* value passed to the compiler.

Before starting specific analysis, it’s essential to understand how standard C++ and UE C++ are compiled because only by distinguishing their fundamental usage differences can we further analyze why these differences exist.

Compiling Standard C++ Code

First, C++ code relies solely on the compiler, as shown in the following code:

1
2
3
4
5
6
7
8
// hw.cpp
#include <iostream>

#define HW_MSG "HelloWorld"
int main()
{
std::cout<<HW_MSG<<std::endl;
}

To compile the above code, a compiler (GCC/MSVC, etc.) is needed. VS uses MSVC, but taking GCC as an example:

1
$ g++ hw.cpp -o hw.exe

This command compiles hw.exe, but the compiler is a toolchain. Although only one command was executed, it actually calls a series of tools for preprocessing, syntax analysis, compilation, linking, and so on, which are not the focus of this article. Those interested can refer to my previous article: Analysis of C/C++ Compilation and Linking Models.
Two points to notice from the above example are:

  1. Standard C++ syntax can be compiled directly with a compiler;
  2. The compiler must perform preprocessing, syntax analysis, compilation, linking, and other operations on the code.

Compiling UEC++ Code

First, UEC++ code cannot be compiled simply by existing in a single file like standard C++. UE has set up its own compilation system, and all code based on the UE engine must be compiled through this system.
I previously wrote some articles on the UE build system; you may want to check these out for UE project build systems:

UEC++ code must depend on a UE project, with a basic project structure like:

1
2
3
4
5
6
7
8
9
Example\GWorld\Source>tree /a /f
| GWorld.Target.cs
|
\---GWorld
GWorld.Build.cs
GWorld.cpp
GWorld.h
Public/
Private/

The responsibilities of each file are:

  • *.Target.cs controls the external compilation environment for the generated executable, the so-called Target. For example, defining what kind of Type (Game/Client/Server/Editor/Program) is generated, whether RTTI is enabled (bForceEnableRTTI), and how CRT linkage is done (bUseStaticCRT), etc.
  • *.Build.cs oversees the Module compilation process, controlling dependencies on other Modules, file inclusions, linking, macro definitions, and other related operations. *.Build.cs informs UE’s build system that it is a Module and specifies what operations to perform during compilation.
  • The remaining files are source code files (typically, header files are in Public/, and implementations are in Private/)

The focus of the UE build system is UBT and UHT, whose roles I discussed in my previous articles:

  • The role of UBT is to collect and build the compilation environment, calling UHT to generate code, and then calling the actual compiler for compilation.
  • The role of UHT is to translate UHT markers in all code within the project into actual C++ code (such as UCLASS/GENERATED_BODY, etc.). This is part of the UBT workflow.

Three key points to note from the above:

  1. UEC++ code must go through UE’s own build system;
  2. UEC++ code must be translated into real C++ code via UHT;
  3. The code that actually enters the compilation stage in a UE project is all standard C++ code.

Thus, the difference between UEC++ and standard C++ lies in the fact that UEC++ defines certain syntax that must be translated through a specialized interpreter before being compiled by a C++ compiler (thus entering the standard C++ compilation process).

That’s all to say regarding process differences; the following content will discuss the special syntax of UE.

Special Syntax of UEC++

The special syntax of UEC++ primarily guides UHT in generating auxiliary code.
Before discussing UEC++’s special syntax, it’s important to clarify a key point: UCLASS/UFUNCTION/UPROPERTY, etc., are not actually meaningful C++ macros; they are UHT markers. After UHT generates the code, they become empty macros and carry no significance.
UE’s code represents both UHT markers and true macros in the form of macros. In terms of results, they both generate some code; however, their processing flows differ. UHT markers are processed first by UHT to generate code before proceeding with preprocessing by the compiler. This introduces a sequential limitation: the treatment of the code by UHT occurs first, while the preprocessing of macros by the compiler occurs afterward, which means that UHT markers cannot be wrapped in macros in UE.

UHT markers include but are not limited to:

More UHT markers can be seen in Runtime\CoreUObject\Public\ObjectMacros.h. A simple way to distinguish them is: if a macro is an empty macro, then it is a UHT marker:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Runtime\CoreUObject\Public\ObjectMacros.h
// ...
// These macros wrap metadata parsed by the Unreal Header Tool, and are otherwise
// ignored when code containing them is compiled by the C++ compiler
#define UPROPERTY(...)
#define UFUNCTION(...)
#define USTRUCT(...)
#define UMETA(...)
#define UPARAM(...)
#define UENUM(...)
#define UDELEGATE(...)

// This pair of macros is used to help implement GENERATED_BODY() and GENERATED_USTRUCT_BODY()
#define BODY_MACRO_COMBINE_INNER(A,B,C,D) A##B##C##D
#define BODY_MACRO_COMBINE(A,B,C,D) BODY_MACRO_COMBINE_INNER(A,B,C,D)

// Include a redundant semicolon at the end of the generated code block, so that intellisense parsers can start parsing
// a new declaration if the line number/generated code is out of date.
#define GENERATED_BODY_LEGACY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY_LEGACY);
#define GENERATED_BODY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY);

#define GENERATED_USTRUCT_BODY(...) GENERATED_BODY()
#define GENERATED_UCLASS_BODY(...) GENERATED_BODY_LEGACY()
#define GENERATED_UINTERFACE_BODY(...) GENERATED_BODY_LEGACY()
#define GENERATED_IINTERFACE_BODY(...) GENERATED_BODY_LEGACY()

#if UE_BUILD_DOCS || defined(__INTELLISENSE__ )
#define UCLASS(...)
#else
#define UCLASS(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_PROLOG)
#endif

#define UINTERFACE(...) UCLASS()
// ...

Here are some points to summarize:

  1. The *.generated.h file is unique to UE, and it contains code generated by UHT (mostly macros).

  2. Markers such as UCLASS/UFUNCTION/UPROPERTY/UENUM do not exist in C++. Their purpose is to guide UHT in generating what auxiliary code.

  3. Macros like GENERATED_BODY also do not exist in standard C++. Their role is to include the code generated by UHT into the current class.

  4. The implementation of a BlueprintNativeEvent function requires adding _Implementation, a rule that doesn’t exist elsewhere, as mentioned earlier that UFUNCTION is not even present in C++.

  5. C++ does not have Slate.

  6. Macros in UE projects are generated in Intermediate\Build\Win64\UE4Editor\Development\MODULE_NAME, e.g., MODULE_NAME_API is an export symbol, which you must define manually in standard C++ projects.

  7. Standard C++ interfaces can be implemented via abstract classes without requiring a specific base class. Furthermore, there is no restriction on providing data members as in UE (at least from a syntax perspective; of course, from a design perspective, interfaces should be stateless).

  8. There are no DELEGATES in standard C++.

  9. Cast<> and NewObject<> are unique to UE, while C++ uses four standard casts and new.

  10. C++ also lacks UE-specific Thunk functions.

Understanding the key difference between UEC++ and standard C++ involves recognizing their differences in the build process, as distinguishing this point allows for a better grasp of all the syntax differences within that structure. As for reflection in UEC++, it’s another extensive topic, and I’ll have to delve into that another time.

How to Learn C++

I started learning C and have nearly ten years of experience now, having read many C++ books. However, I believe the most important thing in learning C++ is to understand why certain features are designed the way they are, what historical constraints they are subject to, and the relationships between these features, followed by examining the compiler implementations of these features. The reasons behind many nuanced elements become clear through this exploration.
Here are some recommended books (I suggest reading them in order; those marked with * are must-reads):

  1. The C Programming Language
  2. *C++ Primer / The C++ Programming Language
  3. *Inside the C++ Object Model
  4. *Effective C++
  5. Modern Effective C++
  6. C++ Coding Standards: 101 Rules, Guidelines, and Best Practices
  7. *The Design and Evolution of C++

The first two books cover the foundational syntax. If you’re short on time, you may read either C++ Primer or TC++PL. I previously wrote an article comparing these two books Reading TC++PL, C++ Primer, and ISO C++, which might be of interest. The journey of learning C++ is long.
I recommend that after mastering the foundational syntax, you start writing a lot of code, as solely understanding theory without practical application is meaningless.

How to Learn UEC++

Once you’ve grasped the fundamental syntax of C++, dive right into writing projects in UE. As an open-source engine (though UE is not open-source software; the licensing is different), there’s nothing hidden. You can write your projects while also examining the code in the UE engine to explore the build process and code generation.

One of my tips is to frequently check the code produced by UHT in Intermediate; many errors or questions can often be answered there.

The most important thing is: read more! Write more! Think more!

The article is finished. If you have any questions, please comment and communicate.

Scan the QR code on WeChat and follow me.

Title:UEC++与标准C++的区别与联系
Author:LIPENGZHA
Publish Date:2020/02/11 11:22
World Count:8.1k Words
Link:https://en.imzlp.com/posts/20425/
License: CC BY-NC-SA 4.0
Reprinting of the full article is prohibited.
Your donation will encourage me to keep creating!