0%

  • FrameThreadContext related to every PerThreadContext in each decoding_threads

  • each PerThreadContext maintains own copy of original AVCodecContext, update_context_from_thread will update its state in case of data-changing passing from previous thread to next thread, such like SPS/PPS data, see ff_h264_update_thread_context

PerThreadContext->state

state means analyze
INPUT_READY cur thread ready for receive next packet Submit Thread (after checking if prev_thread and prev_thread leaving SETTING_UP which means context of current decoding thread is not fully ready) upload input AVPacket and Entering SETTING_UP
SETTING_UP cur thread is on the process of setting context for next decoding thread If this codec got no update_thread_context, means it’s decoding process of each frame is fully isolated. Then After dec-thread received one packet, changing to SETUP_FINISHED is immediately.

Otherwise, changing to SETUP_FINISHED should be done by codec->decode, like happening after SPS Parsed, or multi-thread decoding will be meaningless
SETUP_FINISHED
---
title: "PerThreadContext->state"
---
stateDiagram-v2
    
    [*] --> INPUT_READY
    
    INPUT_READY --> SETTING_UP :  upload packet by SubmitPacket
    SETTING_UP --> SETUP_FINISHED : done works for next dec-thread to running
    SETUP_FINISHED --> INPUT_READY

Sync codes

submit_packet (thread which call avcodec_send_packet)

main function on stack

1
2
3
4
5
6
submit_packet(PerThreadContext * p, AVCodecContext * user_avctx, AVPacket * avpkt) 
ff_thread_decode_frame(AVCodecContext * avctx, AVFrame * picture, int * got_picture_ptr, AVPacket * avpkt)
decode_simple_internal(AVCodecContext * avctx, AVFrame * frame, int64_t * discarded_samples)
decode_simple_receive_frame(AVCodecContext * avctx, AVFrame * frame)
decode_receive_frame_internal(AVCodecContext * avctx, AVFrame * frame)
avcodec_send_packet(AVCodecContext * avctx, const AVPacket * avpkt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
submit_packet(PerThreadContext * p, AVCodecContext * user_avctx, AVPacket * avpkt){
pthread_mutex_lock(&p->mutex);

// wait for previous thread setting done
if (prev_thread) {
while (atomic_load(&prev_thread->state) == STATE_SETTING_UP)
pthread_cond_wait(&prev_thread->progress_cond, &prev_thread->progress_mutex);
}

// upload data and sync
av_packet_unref(p->avpkt);
ret = av_packet_ref(p->avpkt, avpkt);
// this will block next thread, until relevant decoding-thread unlock this by calling ff_thread_finish_setup
atomic_store(&p->state, STATE_SETTING_UP);
pthread_cond_signal(&p->input_cond);
pthread_mutex_unlock(&p->mutex);


fctx->prev_thread = p;
fctx->next_decoding++;
}

frame_worker_thread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static attribute_align_arg void *frame_worker_thread(void *arg){
thread_set_name(p);

while (1) {
while (atomic_load(&p->state) == STATE_INPUT_READY && !p->die)
pthread_cond_wait(&p->input_cond, &p->mutex);

if (!codec->update_thread_context)
ff_thread_finish_setup(avctx);

av_frame_unref(p->frame);
p->got_frame = 0;
p->result = codec->cb.decode(avctx, p->frame, &p->got_frame, p->avpkt);

// in case of cb.decode does not take care of state changing
if (atomic_load(&p->state) == STATE_SETTING_UP)
ff_thread_finish_setup(avctx);

atomic_store(&p->state, STATE_INPUT_READY);
}
}

example: ffh264 calling ff_thread_finish_setup

calling stack

1
2
3
decode_nal_units(H264Context * h, const uint8_t * buf, int buf_size)
h264_decode_frame(AVCodecContext * avctx, AVFrame * pict, int * got_frame, AVPacket * avpkt)
frame_worker_thread(void * arg)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static int decode_nal_units(H264Context *h, const uint8_t *buf, int buf_size){
if (!(avctx->flags2 & AV_CODEC_FLAG2_CHUNKS))
h->current_slice = 0;

ff_h2645_packet_split();

for (i = 0; i < h->pkt.nb_nals; i++){
switch (nal->type) {
case H264_NAL_SLICE:
ff_h264_queue_decode_slice(){
h264_slice_header_parse(h, sl, nal);
h264_slice_init(){
sl->slice_num = ++h->current_slice;
}
}
ff_thread_finish_setup(avctx);
// see, decoding after passing signal of SETUP_FINISHED
ff_h264_execute_decode_slices(h);
}
}

这篇文章虽是在h264系列里,耐不住JEPG wiki写得太nice了,所以忍不住引用进来

JPEG 编码过程

graph LR
I(原图)-->|"ColorSpace Transform+DownSampling"|YCbCr("YCbCr颜色空间")
YCbCr-->|Split|block(8x8块)
block-->|DCT|FD("频域(能量集中)")
FD-->|Quantization|Qed(降低精度)
Qed-->|"ZigZag+RLE+Entropy"|Out("输出")

DCT转换对于一般的编码格式都是极为重要的

下面简单介绍一下各阶段

    1. ColorSpace Transform+DownSampling

    老生常谈的YUV420采样,编码压缩第一步。某些high quality encoder可能会保持在RGB空间(担心颜色空间转换时的损失呗)

    1. Split

    分割Y Cb Cr的平面,分开编码,宏块对齐到8*8

    1. DCT

    原输入的无损转换,本质是 $C=DID^T$, 坐标转换,将原图从空间域,转换到频域

    由于原图中能量最高的部分(信息量最大)一般都在低频区域,所以叫做能量集中

    此后都在DCT的频域上操作

    1. Quantization(量化)

    降低频率信息的精度(这会影响熵编码时的开销)

    根据参考的编码质量(以及相关视觉研究结果),对图像各频率的数据采用不同的scale系数量化(h264也有scale list)

II-DCT form

$$
D_{h,v}(m,n) = 2\sum_{m=0}^{M-1}\sum_{n=0}^{N-1}\frac{1}{\sqrt{M}}\frac{1}{\sqrt{N}}\eta_h\eta_{v} \cos{\frac{\pi h}{2M}(2m+1)}\cos{\frac{\pi v}{2N}(2n+1)}
$$

可视化频域

DCT

code which gen this

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
import numpy as np
import matplotlib.pyplot as plt

nRow = 8
nCol = 8
nFreqHoriz = 8
nFreqVerti = 8
normFreqDC = 1/np.sqrt(2)

for iFreqH in range(nFreqHoriz):
for iFreqV in range(nFreqVerti):
iFreqHoriz = iFreqH
iFreqVerti = iFreqV
matFreq2D = np.zeros((nRow,nCol))
for iRow in range(nRow):
for iCol in range(nCol):
iRow0 = iRow
iCol0 = iCol
matFreq2D[iRow,iCol] = 2/(np.sqrt(nFreqHoriz)*np.sqrt(nFreqVerti))*np.cos(np.pi*iFreqHoriz*(2*iRow0+1)/(2*nRow))*np.cos(np.pi*iFreqVerti*(2*iCol0+1)/(2*nCol))
if iFreqHoriz == 0:
matFreq2D[iRow,iCol] = matFreq2D[iRow,iCol]*normFreqDC
if iFreqVerti == 0:
matFreq2D[iRow,iCol] = matFreq2D[iRow,iCol]*normFreqDC
print(np.sum(matFreq2D**2))
plt.subplot(nFreqHoriz,nFreqVerti,8*iFreqV+iFreqH+1)
plt.imshow(matFreq2D, cmap='gray')
plt.axis('off')
plt.show()

大致配置命令如下
但在配置之前,我们需要关注一下msvc配置的语言(中文?)与Windows Path对于依赖文件*.d的影响

1
2
3
4
5
# 初始化 msvc 环境
cmd /k "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
# 进入wsl 环境, 试试configure。
wsl -d u18
./configure --toolchain=msvc --enable-gpl --extra-cflags=/utf-8 --enable-shared --disable-stripping --disable-optimizations --disable-doc --prefix=`pwd`/../install

头文件中的中文字符

config.h: #define CC_IDENT "用于 x64 的 Microsoft (R) C/C++ 优化编译器 19.16.27051 版"
可能需要移除,或者翻译,或者转码

转码脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

file=$1

if [[ $(file -i $file) == *"charset=utf-8"* ]]; then
echo "$file is already utf-8 encoded"
else
echo "$file is not utf-8 encoded"
# iconv -f $(file -bi $file | awk -F "=" '{print $2}') -t utf-8 $file > ${file}.utf8
iconv -f GBK -t utf-8 "$file" > "$file.temp"
mv "$file.temp" "$file"

fi

Windows路径格式

但此时编译可能会遇到 .d 依赖描述文件的路径格式不兼容问题
例如,手动执行以下语句

1
2
3
4
5
6
7
cl.exe -D__STDC_CONSTANT_MACROS -nologo /utf-8 -I. -utf-8 -showIncludes libavcodec/4xm.c
注意: 包含文件: h:\code\ot\dxva\ffmpeg\libavcodec\get_bits.h
注意: 包含文件: h:\code\ot\dxva\ffmpeg\libavcodec\mathops.h
注意: 包含文件: H:\code\ot\dxva\ffmpeg\libavutil/attributes_internal.h
注意: 包含文件: h:\code\ot\dxva\ffmpeg\libavcodec\x86/mathops.h
注意: 包含文件: H:\code\ot\dxva\ffmpeg\libavutil/x86/asm.h
注意: 包含文件: h:\code\ot\dxva\ffmpeg\libavcodec\vlc.h

注意到configure 中处理路径格式的片段

1
2
_DEPCMD='$(DEP$(1)) $(DEP$(1)FLAGS) $($(1)DEP_FLAGS) $< 2>&1 | awk '\''/including/ { sub(/^.*file: */, ""); gsub(/\\/, "/"); if (!match($$0, / /)) print "$@:", $$0 }'\'' > $(@:.o=.d)'
_DEPFLAGS='$(CPPFLAGS) $(CFLAGS) -showIncludes -Zs'

是针对英文与unix版路径的,所以需要替换

中文路径

使用 iconv 做管道转码 并更改sed参数
你可以验证以下命令

1
2
3
$ cl.exe -showIncludes -utf-8  -I. libavcodec/4xm.c 2>&1 |\
iconv -f GBK -t utf-8 |\
awk '/包含文件/ { sub(/^.*包含文件: */, ""); gsub(/\\/, "/"); if (!match($0, / /)) { b=substr($0,3); $0=gensub(/(\w):(.*)/,"/mnt/\\1" b,1,tolower($0)); print "$@:", $0 } }'

windows路径

1
2
3
$ cl.exe -showIncludes -utf-8  -I. libavcodec/4xm.c 2>&1 |\
iconv -f GBK -t utf-8 |\
awk '/including/ { sub(/^.*file: */, ""); gsub(/\\/, "/"); if (!match($$0, / /)) { b=substr($$0,3); $$0=gensub(/(\w):(.*)/,"/mnt/\\1" b,1,tolower($$0)); print "$@:", $$0 } }'

替换诸如 Note: including file: D:\code\ln\FFmpeg\ffmpeg\libavutil/internal.h

libavfilter/vf_blackframe.o: /mnt/d/code/ln/FFmpeg/ffmpeg/libavutil/internal.h

这一段核心在于 if (!match($$0, / /)) { b=substr($$0,3); $$0=gensub(/(\w):(.*)/,"/mnt/\\1" b,1,tolower($$0));

  1. if (!match($$0, / /)) 意为仅处理一级依赖。例如cl.exe /showIncludes,会打印嵌套includes,这是我们要避免的
  2. b=substr($$0,3); 保存部分路径( code/ln/FFmpeg/ffmpeg/libavutil/internal.h )
  3. $$0=gensub(/(\w):(.*)/,"/mnt/\\1" b,1,tolower($$0));
    1. tolower($$0) 这是gensub的原字符串参数,我们将原行转换为小写输入
    2. "/(\w):(.*)/" 第一个参数意为拆分匹配行,分离 windows 驱动器符 与子路径 (注意 // 包裹的字符串是正则字符串
    3. "/mnt/\\1" b 注意b 这里有个字符串追加动作哦。 意为输出串是 "/mnt/" + 匹配的第一个参数(也即windows驱动器符号(小写)) + 保留的部分源字符串

综合

注意如果vs不在中文环境的话,只需替换wsl路径就可以了
总之,综合起来就是
注意,控制台或者configure文件的编码是utf8,所以要求awk处理的中文字符也是utf编码的, 这是我的理解

1
2
3
$ cl.exe -showIncludes -utf-8  -I. libavcodec/4xm.c 2>&1 |\
iconv -f GBK -t utf-8 |\
awk '/包含文件|including/ { sub(/^.*包含文件: *|^.*file: */, ""); gsub(/\\/, "/"); if (!match($$0, / /)) { b=substr($$0,3); $$0=gensub(/(\w):(.*)/,"/mnt/\\1" b,1,tolower($$0)); print "$@:", $$0 } }'

总结

修改configure的内容

1
2
3
4
5
6
7
8
9
10
#  elif $_cc -nologo- 2>&1 | grep -q Microsoft || { $_cc -v 2>&1 | grep -q clang && $_cc -? > /dev/null 2>&1; }; then
# _type=msvc
# if $_cc -nologo- 2>&1 | grep -q Microsoft; then
# _ident=$($_cc 2>&1 | head -n1 | tr -d '\r')
# else
# _ident=$($_cc --version 2>/dev/null | head -n1 | tr -d '\r')
# fi
# - _DEPCMD='$(DEP$(1)) $(DEP$(1)FLAGS) $($(1)DEP_FLAGS) $< 2>&1 | awk '\''/including/ { sub(/^.*file: */, ""); gsub(/\\/, "/"); if (!match($$0, / /)) print "$@:", $$0 }'\'' > $(@:.o=.d)'
# +
_DEPCMD='$(DEP$(1)) $(DEP$(1)FLAGS) $($(1)DEP_FLAGS) $< 2>&1 | iconv -f GBK -t utf-8 | awk '\''/包含文件|including/ { sub(/^.*包含文件: *|^.*file: */, ""); gsub(/\\/, "/"); if (!match($$0, / /)) { b=substr($$0,3); $$0=gensub(/(\w):(.*)/,"/mnt/\\1" b,1,tolower($$0)); print "$@:", $$0 } }'\'' > $(@:.o=.d)'

configure

1
2
3
4
5
6
7
8
9
10
11
# 1. 初始化 msvc 环境
cmd /k "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
# 2. ... 修改configure的dep gen配置
# 3. 进入wsl 环境
wsl -d u18
# 4. config
./configure --toolchain=msvc --enable-gpl --extra-cflags=/utf-8 --enable-shared --disable-stripping --disable-optimizations --disable-doc --prefix=`pwd`/../install
# 5. 修改 config.h 中的中文CC_INDENT...
# 6. 手动修改 ffbuild/config.mak 中的编译配置也可,例如增加编译选项
# 7. make
make -j

cl.exe -showIncludes -utf-8 -I. libavcodec/4xm.c 2>&1 | iconv -f GBK -t utf-8 | awk ‘/包含文件|including/ { sub(/^.*包含文件: *|^.*file: /, “”); gsub(/\/, “/“); if (!match($$0, / /)) { b=substr($$0,3); $$0=gensub(/(\w):(.)/,”/mnt/\1” b,1,tolower($$0)); print “$@:”, $$0 } }’

更多

手动修改编译选项

关注 ffbuild/config.mak
由于编译时出现了一堆警告 libavfilter/af_adelay.c(99): warning C4267: “初始化”: 从“size_t”转换到“const int”,可能丢失数据
故手动添加诸如 -wd4267 之类的标志

1
CFLAGS= -nologo /utf-8  -Z7 -W3 -wd4018 -wd4133 -wd4146 -wd4244 -wd4267 -wd4305 -wd4334 -wd4554 -O1 -utf-8

附录

追踪 dep gen的命令路径:

  • configure
  • ffbuild/config.mak
  • make -n > make.log

clang::ParseAST main loop for the stmts parsing;

1
2
3
4
5
6
7
8
for (bool AtEOF = P.ParseFirstTopLevelDecl(ADecl, ImportState); !AtEOF;
AtEOF = P.ParseTopLevelDecl(ADecl, ImportState)) {
// If we got a null return and something *was* parsed, ignore it. This
// is due to a top-level semicolon, an action override, or a parse error
// skipping something.
if (ADecl && !Consumer->HandleTopLevelDecl(ADecl.get())) // CodeGen
return;
}

Parser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// printf(    $tok place$ "xxx")
> clang::Parser::ParseExpressionList(llvm::SmallVectorImpl & Exprs, llvm::function_ref ExpressionStarts, bool FailImmediatelyOnInvalidExpr, bool EarlyTypoCorrection)
clang::Parser::ParsePostfixExpressionSuffix(clang::ActionResult LHS)
clang::Parser::ParseCastExpression(clang::Parser::CastParseKind ParseKind, bool isAddressOfOperand, bool & NotCastExpr, clang::Parser::TypeCastState isTypeCast, bool isVectorLiteral, bool * NotPrimaryExpression)
clang::Parser::ParseCastExpression(clang::Parser::CastParseKind ParseKind, bool isAddressOfOperand, clang::Parser::TypeCastState isTypeCast, bool isVectorLiteral, bool * NotPrimaryExpression)
clang::Parser::ParseAssignmentExpression(clang::Parser::TypeCastState isTypeCast)
clang::Parser::ParseExpression(clang::Parser::TypeCastState isTypeCast)
clang::Parser::ParseExprStatement(clang::Parser::ParsedStmtContext StmtCtx)
clang::Parser::ParseStatementOrDeclarationAfterAttributes(llvm::SmallVector & Stmts, clang::Parser::ParsedStmtContext StmtCtx, clang::SourceLocation * TrailingElseLoc, clang::ParsedAttributes & CXX11Attrs, clang::ParsedAttributes & GNUAttrs)
clang::Parser::ParseStatementOrDeclaration(llvm::SmallVector & Stmts, clang::Parser::ParsedStmtContext StmtCtx, clang::SourceLocation * TrailingElseLoc)
clang::Parser::ParseCompoundStatementBody(bool isStmtExpr)
clang::Parser::ParseFunctionStatementBody(clang::Decl * Decl, clang::Parser::ParseScope & BodyScope)
clang::Parser::ParseFunctionDefinition(clang::ParsingDeclarator & D, const clang::Parser::ParsedTemplateInfo & TemplateInfo, clang::Parser::LateParsedAttrList * LateParsedAttrs)
clang::Parser::ParseDeclGroup(clang::ParsingDeclSpec & DS, clang::DeclaratorContext Context, clang::ParsedAttributes & Attrs, clang::SourceLocation * DeclEnd, clang::Parser::ForRangeInit * FRI)
clang::Parser::ParseDeclOrFunctionDefInternal(clang::ParsedAttributes & Attrs, clang::ParsedAttributes & DeclSpecAttrs, clang::ParsingDeclSpec & DS, clang::AccessSpecifier AS)
clang::Parser::ParseDeclarationOrFunctionDefinition(clang::ParsedAttributes & Attrs, clang::ParsedAttributes & DeclSpecAttrs, clang::ParsingDeclSpec * DS, clang::AccessSpecifier AS)
clang::Parser::ParseExternalDeclaration(clang::ParsedAttributes & Attrs, clang::ParsedAttributes & DeclSpecAttrs, clang::ParsingDeclSpec * DS)
clang::Parser::ParseTopLevelDecl(clang::OpaquePtr & Result, clang::Sema::ModuleImportState & ImportState)
clang::ParseAST(clang::Sema & S, bool PrintStats, bool SkipFunctionBodies)
clang::ASTFrontendAction::ExecuteAction()
clang::CodeGenAction::ExecuteAction()
clang::FrontendAction::Execute()
clang::CompilerInstance::ExecuteAction(clang::FrontendAction & Act)

name lookup

OverloadCandidate
isBetterOverloadCandidate

1
2
3
4
clang::OverloadCandidateSet::BestViableFunction(clang::Sema & S, clang::SourceLocation Loc, clang::OverloadCandidate * & Best)
clang::Sema::BuildOverloadedCallExpr(clang::Scope * S, clang::Expr * Fn, clang::UnresolvedLookupExpr * ULE, clang::SourceLocation LParenLoc, llvm::MutableArrayRef Args, clang::SourceLocation RParenLoc, clang::Expr * ExecConfig, bool AllowTypoCorrection, bool CalleesAddressIsTaken)
clang::Sema::BuildCallExpr(clang::Scope * Scope, clang::Expr * Fn, clang::SourceLocation LParenLoc, llvm::MutableArrayRef ArgExprs, clang::SourceLocation RParenLoc, clang::Expr * ExecConfig, bool IsExecConfig, bool AllowRecovery)
clang::Sema::ActOnCallExpr(clang::Scope * Scope, clang::Expr * Fn, clang::SourceLocation LParenLoc, llvm::MutableArrayRef ArgExprs, clang::SourceLocation RParenLoc, clang::Expr * ExecConfig)

Dialogs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
TextDiagnostic::emitDiagnosticMessage(clang::FullSourceLoc Loc, clang::PresumedLoc PLoc, clang::DiagnosticsEngine::Level Level, llvm::StringRef Message, llvm::ArrayRef<clang::CharSourceRange> Ranges, llvm::PointerUnion<clang::Diagnostic const *,clang::StoredDiagnostic const *> D)
DiagnosticRenderer::emitDiagnostic(clang::FullSourceLoc Loc, clang::DiagnosticsEngine::Level Level, llvm::StringRef Message, llvm::ArrayRef<clang::CharSourceRange> Ranges, llvm::ArrayRef<clang::FixItHint> FixItHints, llvm::PointerUnion<clang::Diagnostic const *,clang::StoredDiagnostic const *> D)
TextDiagnosticPrinter::HandleDiagnostic(clang::DiagnosticsEngine::Level Level, const clang::Diagnostic & Info)
DiagnosticIDs::EmitDiag(clang::DiagnosticsEngine & Diag, clang::DiagnosticIDs::Level DiagLevel)
DiagnosticIDs::ProcessDiag(clang::DiagnosticsEngine & Diag)
DiagnosticsEngine::ProcessDiag()
DiagnosticsEngine::EmitCurrentDiagnostic(bool Force)
DiagnosticBuilder::Emit()
DiagnosticBuilder::~DiagnosticBuilder()
Parser::ParseCastExpression(clang::Parser::CastParseKind ParseKind, bool isAddressOfOperand, clang::Parser::TypeCastState isTypeCast, bool isVectorLiteral, bool * NotPrimaryExpression)
Parser::ParseRHSOfBinaryExpression(clang::ActionResult<clang::Expr *,1> LHS, clang::prec::Level MinPrec)
Parser::ParseAssignmentExpression(clang::Parser::TypeCastState isTypeCast)
Parser::ParseExpression(clang::Parser::TypeCastState isTypeCast)
Parser::ParseExprStatement(clang::Parser::ParsedStmtContext StmtCtx)
Parser::ParseStatementOrDeclarationAfterAttributes(llvm::SmallVector<clang::Stmt *,32> & Stmts, clang::Parser::ParsedStmtContext StmtCtx, clang::SourceLocation * TrailingElseLoc, clang::ParsedAttributes & CXX11Attrs, clang::ParsedAttributes & GNUAttrs)
Parser::ParseStatementOrDeclaration(llvm::SmallVector<clang::Stmt *,32> & Stmts, clang::Parser::ParsedStmtContext StmtCtx, clang::SourceLocation * TrailingElseLoc)
Parser::ParseCompoundStatementBody(bool isStmtExpr)
Parser::ParseFunctionStatementBody(clang::Decl * Decl, clang::Parser::ParseScope & BodyScope)
Parser::ParseFunctionDefinition(clang::ParsingDeclarator & D, const clang::Parser::ParsedTemplateInfo & TemplateInfo, clang::Parser::LateParsedAttrList * LateParsedAttrs)
Parser::ParseDeclGroup(clang::ParsingDeclSpec & DS, clang::DeclaratorContext Context, clang::ParsedAttributes & Attrs, clang::SourceLocation * DeclEnd, clang::Parser::ForRangeInit * FRI)

codeGen

entry point

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
clang::CodeGen::CodeGenFunction::GenerateCode(clang::GlobalDecl GD, llvm::Function * Fn, const clang::CodeGen::CGFunctionInfo & FnInfo)
{
///* int main(){ */
CodeGenFunction::StartFunction(GD, ResTy, Fn, FnInfo, Args, Loc, BodyRange.getBegin())
{
CodeGenFunction::EmitFunctionProlog()
{
// If this- is an implicit-return-zero function, go ahead and
// initialize the return value. TODO: it might be nice to have
// a more general mechanism for this that didn't require synthesized
// return statements.
if (const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(CurCodeDecl)) {
if (FD->hasImplicitReturnZero()) {
QualType RetTy = FD->getReturnType().getUnqualifiedType();
llvm::Type* LLVMTy = CGM.getTypes().ConvertType(RetTy);
llvm::Constant* Zero = llvm::Constant::getNullValue(LLVMTy);
Builder.CreateStore(Zero, ReturnValue);
}
}
}
}

///* printf("xxx"); */
CodeGenFunction::EmitFunctionBody(const Stmt *Body)->
CodeGenFunction::EmitCompoundStmtWithoutScop(const CompoundStmt &S,bool GetLast,AggValueSlot AggSlot)
{
for (auto *CurStmt : S.body())
EmitStmt(CurStmt);
}
}

stmt entry

1
2
3
4
5
6
7
8
clang::CodeGen::CodeGenFunction::EmitStmt(const clang::Stmt * S, llvm::ArrayRef<...> Attrs)
clang::CodeGen::CodeGenFunction::EmitCompoundStmtWithoutScope(const clang::CompoundStmt & S, bool GetLast, clang::CodeGen::AggValueSlot AggSlot)
clang::CodeGen::CodeGenFunction::EmitFunctionBody(const clang::Stmt * Body)
clang::CodeGen::CodeGenFunction::GenerateCode(clang::GlobalDecl GD, llvm::Function * Fn, const clang::CodeGen::CGFunctionInfo & FnInfo)
clang::CodeGen::CodeGenModule::EmitGlobalFunctionDefinition(clang::GlobalDecl GD, llvm::GlobalValue * GV)
clang::CodeGen::CodeGenModule::EmitGlobalDefinition(clang::GlobalDecl GD, llvm::GlobalValue * GV)
clang::CodeGen::CodeGenModule::EmitGlobal(clang::GlobalDecl GD)
clang::CodeGen::CodeGenModule::EmitTopLevelDecl(clang::Decl * D)

other

Breakpoint on Instruction::Instruction ctor for IR code generation
See CodeGenFunction::EmitFunctionProlog for C++ main defaultly return 0

showcase

  1. EqualToAnyOf
    1
    2
    3
    // 注意 递归单元
    int var = 3;
    EqualToAnyOf(var, 1, 2, 3); // 0 || (var == 1) || (var == 2) || (var == 3)
  2. MAKE_ENUM
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    MAKE_ENUM(EE,"name_prefix",E1,E2,E3);
    // 展开到
    enum class EE {E1, E2,E3 };
    const char *GetStr(EE e)
    {g
    switch (e)
    {
    case EE::E1:
    return "name_prefix" "E1";
    case EE::E2:
    return "name_prefix" "E2";
    case EE::E3:
    return "name_prefix" "E3";
    default:
    return "name_prefix" "";
    }
    };

介绍

1
2
3
4
// prototype
#define FOREACH(pps,...) ...
// acutually params
#define FOREACH(pps,it_operator,...)

pps: 打包参数,形如 (arg1), (arg1,arg2)
it_operator: 迭代宏函数,如此使用:ite_operator(pps,it)
...: 迭代参数集合,展开后作为 ite_oprator的最后一个参数

实现

辅助宏函数

  1. __Choose30th

    1
    2
    3
    4
    #define __Choose30th(_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,
    ...) _30
  2. ID
    msvc默认的宏展开有一点区别于gcc like编译器,所以老是需要用ID去做展开, 当然msvc也支持相关选项切换到gcc like的宏展开行为

    1
    2
    #define ID(_1, ...) _1
    #define IDx(...) __VA_ARGS__
  3. Callx + CallxPPS

    1
    2
    #define Callx(f, ...) IDx(f(__VA_ARGS__))
    #define CallxPPS(f, _1) IDx(f _1)

    两种形式的宏函数代理调用
    因为当我遇到 MacroFunc(IDx pps, otherArgs)时,外部套一层 IDx也无法使展开的pps作为分离参数传入MacroFunc

    感觉 ID只能辅助展开__VA_ARGS__,所以这里 Callx(MacroFunc, IDx pps, otherArgs) 将所有参数压缩成变长参数之后再由 Callx重新展开

过程

OK, finally.

Show me the code

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// clang-format off
#define __Choose30th(_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,\
...) _30
#define ID(_1, ...) _1
#define IDx(...) __VA_ARGS__
#define Callx(f, ...) ID(f(__VA_ARGS__))
#define CallxPPS(f, _1) ID(f _1)
#define FOREACH0(it_operator, pps, ...)
#define FOREACH1(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH0 (it_operator, pps, __VA_ARGS__))
#define FOREACH2(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH1 (it_operator, pps, __VA_ARGS__))
#define FOREACH3(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH2 (it_operator, pps, __VA_ARGS__))
#define FOREACH4(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH3 (it_operator, pps, __VA_ARGS__))
#define FOREACH5(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH4 (it_operator, pps, __VA_ARGS__))
#define FOREACH6(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH5 (it_operator, pps, __VA_ARGS__))
#define FOREACH7(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH6 (it_operator, pps, __VA_ARGS__))
#define FOREACH8(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH7 (it_operator, pps, __VA_ARGS__))
#define FOREACH9(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH8 (it_operator, pps, __VA_ARGS__))
#define FOREACH10(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH9 (it_operator, pps, __VA_ARGS__))
#define FOREACH11(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH10(it_operator, pps, __VA_ARGS__))
#define FOREACH12(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH11(it_operator, pps, __VA_ARGS__))
#define FOREACH13(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH12(it_operator, pps, __VA_ARGS__))
#define FOREACH14(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH13(it_operator, pps, __VA_ARGS__))
#define FOREACH15(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH14(it_operator, pps, __VA_ARGS__))
#define FOREACH16(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH15(it_operator, pps, __VA_ARGS__))
#define FOREACH17(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH16(it_operator, pps, __VA_ARGS__))
#define FOREACH18(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH17(it_operator, pps, __VA_ARGS__))
#define FOREACH19(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH18(it_operator, pps, __VA_ARGS__))
#define FOREACH20(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH19(it_operator, pps, __VA_ARGS__))
#define FOREACH21(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH20(it_operator, pps, __VA_ARGS__))
#define FOREACH22(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH21(it_operator, pps, __VA_ARGS__))
#define FOREACH23(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH22(it_operator, pps, __VA_ARGS__))
#define FOREACH24(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH23(it_operator, pps, __VA_ARGS__))
#define FOREACH25(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH24(it_operator, pps, __VA_ARGS__))
#define FOREACH26(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH25(it_operator, pps, __VA_ARGS__))
#define FOREACH27(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH26(it_operator, pps, __VA_ARGS__))
#define FOREACH28(it_operator, pps, _1, ...) IDx(it_operator(pps, _1) FOREACH27(it_operator, pps, __VA_ARGS__))
// clang-format on
#define FOREACH(pps, ...) \
ID(__Choose30th(__VA_ARGS__, \
FOREACH28, \
FOREACH27, \
FOREACH26, \
FOREACH25, \
FOREACH24, \
FOREACH23, \
FOREACH22, \
FOREACH21, \
FOREACH20, \
FOREACH19, \
FOREACH18, \
FOREACH17, \
FOREACH16, \
FOREACH15, \
FOREACH14, \
FOREACH13, \
FOREACH12, \
FOREACH11, \
FOREACH10, \
FOREACH9, \
FOREACH8, \
FOREACH7, \
FOREACH6, \
FOREACH5, \
FOREACH4, \
FOREACH3, \
FOREACH2, \
FOREACH1, \
FOREACH0)) \
IDx((pps, __VA_ARGS__))

Use Case

  1. EqualToAnyOf
1
2
3
#define __EqualToPlain(x, y) || (x == y)
#define __EqualToPacket(px, y) Callx(__EqualToPlain, IDx px, y)
#define EqualToAnyOf(val, ...) 0 FOREACH(__EqualToPacket, (val), __VA_ARGS__)
  1. MAKE_ENUM
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#define MAKE_ENUM_Declare(ClsName,...) enum class ClsName{__VA_ARGS__};
#define MAKE_ENUM_ITEM_Begin(ClsName) \
const char *GetStr(ClsName e) \
{ \
switch (e) \
{
#define MAKE_ENUM_ITEM(ClsName, Prefix, name) \
case ClsName::name: \
return Prefix #name;
// #define MAKE_ENUM_ITEM_PPS(pps, item) CallxPPS( Callx,(MAKE_ENUM_ITEM, IDx pps, item))
#define MAKE_ENUM_ITEM_PPS(pps, item) Callx(MAKE_ENUM_ITEM, IDx pps, item)
#define MAKE_ENUM_ITEM_End(ClsName, Prefix) \
default: \
return Prefix ""; \
} \
}
#define MAKE_ENUM(ClsName, Prefix, ...) \
MAKE_ENUM_Declare(ClsName, __VA_ARGS__) \
MAKE_ENUM_ITEM_Begin(ClsName) \
IDx(FOREACH(MAKE_ENUM_ITEM_PPS, (ClsName, Prefix), __VA_ARGS__)) \
MAKE_ENUM_ITEM_End(ClsName, Prefix);

附录

  1. 检查 预处理器输出

​ 即使是vs2022默认的智能感知引擎也可能无法在hover tip上正确显示预处理的结果,所以需要手动用编译器展开观察中间结果

1
2
3
4
5
6
7
8
9
10
11
12
13
# MSVC	>> foreach_macro.i
while($true)
{
cl /P foreach_macro.cpp
sleep 1
}

# CLANG >> stdout (or append "-o foreach_macro.i")
while($true)
{
clang -E foreach_macro.cpp
sleep 1
}

系统: ubuntu 24.04(wsl)

gimp 依赖大量gnu 组件,windows编译较为麻烦,故此使用wsl

1. code

拉取GIMP 及某些依赖的代码自无甚讲头
目录树如下组织

1
2
3
4
5
├── 3rd
│   ├── babl
│   ├── gegl
├── install
└── gimp
1
2
3
git clone git@github.com:GNOME/gimp.git gimp
git clone https://gitlab.gnome.org/GNOME/gegl.git 3rd/gegl
git clone https://gitlab.gnome.org/GNOME/babl.git/ 3rd/babl

2. env

依赖组件默认使用系统源安装,仅当需要时再手动编译

1
2
3
4
5
sudo apt build-dep babl -y
sudo apt build-dep gegl -y
sudo apt build-dep gimp -y
# there are some missing deps, if you got more, see #more.deps-founding
sudo apt install libgtk-3-dev libappstream-glib-dev -y

3. build

3.1 build babl+gegl

babl is simple

1
2
meson setup mbuild  --buildtype=debug --prefix=`pwd`/../../install
ninja install -C mbuild/

gegl may need check a buildable branch

1
2
3
4
5
6
git switch GEGL_0_4_44
git branch GEGL_0_4_44
git checkout GEGL_0_4_44
# gegl docs building have got some error there
meson setup mbuild -Ddocs=false --buildtype=debug --prefix=`pwd`/../../install
ninja install -C mbuil

3.2 build gimp


pkg config file of babl may mismatch with gimp needs

ln -s install/lib/x86_64-linux-gnu/pkgconfig/babl-0.1.pc install/lib/x86_64-linux-gnu/pkgconfig/babl.pc


since we build GEGL_0_4_44, so we may adjust gimp version

1
2
3
4
5
6
7
8
9
10
git log|grep "depend on GEGL" -4
# found this
# commit b85032d8b6bceb1114452bffb53754a65d977f50
# Author: Øyvind Kolås <pippin@gimp.org>
# Date: Sun Nov 13 17:16:49 2022 +0100

# meson, configure, app: depend on GEGL 0.4.40
git checkout b85032d8b6bceb1114452bffb53754a65d977f50
git branch gegl44
git switch gegl44
1
2
export PKG_CONFIG_PATH+=:/mnt/d/code/ln/gimp/install/lib/x86_64-linux-gnu/pkgconfig 
meson setup mbuild --buildtype=debug --prefix=`pwd`/../install

debug env

dont forgot install gdb sudo apt install gdb

this is a available launch.json config
note:

  • -enable-pretty-printing is harmful for vscode debugger module
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
35
{
"version": "0.2.0",
"configurations": [
{
"name": "install/gimp-2.99",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/install/bin/gimp-2.99",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/install/bin",
"environment": [
{
"name": "LD_LIBRARY_PATH",
"value": ".:${workspaceFolder}/install/lib/x86_64-linux-gnu:$ENV{LD_LIBRARY_PATH}"
}
],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
// this may lead to vscode failing to list var's member(GObject) on WATCH window
// {
// "description": "Enable pretty-printing for gdb",
// "text": "-enable-pretty-printing",
// "ignoreFailures": true
// },
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
}
]
}

appendix

1. display setting

wsl中调试图形程序需要x server, 使用 vcxsrv 或者 X410 均可

然后cli环境 export DISPLAY=127.0.0.1:0 即可

注意本地环回地址貌似在wsl2中无法使用,需要获取windows的wsl虚拟网络适配器的网络地址

install code server on ubuntu
using vscode in browser

install code server

1
2
3
4
sudo apt update
curl -fsSL https://code-server.dev/install.sh | sh
sudo apt install ~/Downloads/code-server_*_amd64.deb
# sudo systemctl enable --now code-server@$USER

config code server

配置文件 ~/.config/code-server/config.yaml
密码是你登录时需要输入的

1
2
3
bind-addr: 0.0.0.0:8082
auth: password
password: xxxyyy # your customed password

check running

on one terminal, just type code-server, it will automatically using config file listed above
on another terminal, type

1
2
curl http://localhost:8082 # ok if local-machine alright
curl http://xx.xx.ipv4:8082 # if failed, need check firewall settings

more

add autostart service

edit service file /etc/systemd/system/my-code-server.service

1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=Code Server

[Service]
User=lull
ExecStart=/usr/bin/code-server
Restart=always

[Install]
WantedBy=multi-user.target

start service: sudo systemctl start my-code-server
set autostart: sudo systemctl enable my-code-server

xray forward

add xray config file xray-rootdir/conf/99_code-server.json
this wil redirect incoming 0.0.0.0:7401 into localhost:8082

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
35
36
37
38
39
40
41
{
"inbounds": [
{
"listen": "0.0.0.0",
"port": 7401,
"protocol": "dokodemo-door",
"settings": {
"network": "tcp",
"address": "127.0.0.1",
"port": 8082,
"followRedirect": false
},
"sniffing": {
"enabled": true,
"destOverride": ["http", "tls"]
}
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {},
"tag": "direct"
},
{
"protocol": "blackhole",
"settings": {},
"tag": "blocked"
}
],
"routing": {
"rules": [
{
"type": "field",
"ip": ["0.0.0.0/0"],
"outboundTag": "direct"
}
]
}
}

enable SSL Certificate

  1. using certbot to generate certs

    1
    2
    3
    4
    5
    6
    7
    sudo apt instal certbot
    sudo certbot certonly --standalone -d www.yourdomain.com
    # Successfully received certificate.
    # Certificate is saved at: /etc/letsencrypt/live/www.yourdomain.com/fullchain.pem
    # Key is saved at: /etc/letsencrypt/live/www.yourdomain.com/privkey.pem
    # This certificate expires on 2024-03-02.
    # These files will be updated when the certificate renews.
  2. change service to run as root
    vim /etc/systemd/system/my-code-server.service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [Unit]
    Description=Code Server

    [Service]
    User=root
    ExecStart=/usr/bin/code-server
    Restart=always

    [Install]
    WantedBy=multi-user.target
  3. change code-server config

    1
    2
    3
    4
    5
    6
    bind-addr: 0.0.0.0:8082
    auth: password
    log: debug
    password: yourdomain
    cert: /etc/letsencrypt/live/code.yourdomain.com/cert.pem
    cert-key: /etc/letsencrypt/live/code.yourdomain.com/privkey.pem
  4. restart service

    1
    2
    sudo systemctl daemon-reload
    sudo systemctl restart my-code-server.service

enable SSL Certificate( as normal user)

  1. same to enable-ssl-certificate, generate keys first, note cerbot keep multi version keys for same domain name, see ll /etc/letsencrypt/live/code.yourdomain.com/cert.pem

  2. copy cert files

    1
    2
    3
    # copy real cert file not symbolic link
    sudo cp /etc/letsencrypt/live/code.yourdomain.com/ -rL .
    sudo chown lull:lull code.yourdomain.com/*
  3. change code-server config
    vim ~/.config/code-server/config.yaml
    note: service started by system, run as user, so keep cert path as absolute

    1
    2
    3
    4
    5
    bind-addr: 0.0.0.0:8082
    auth: password
    password: yourdomain
    cert: /home/lull/.config/code-server/code.yourdomain.com/cert.pem
    cert-key: /home/lull/.config/code-server/code.yourdomain.com/privkey.pem
  4. revert service config to run as normal user
    vim /etc/systemd/system/my-code-server.service

1
2
[Service]
User=lull
  1. restart service

切换到旧版右键菜单

1
2
reg add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32" /f /ve
taskkill /f /im explorer.exe & start explorer.exe

切换回新版

1
2
reg delete "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}" /f
taskkill /f /im explorer.exe & start explorer.exe

Visitor 表示作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
在我理解,Visitor 就是把离散分布在 子类中的某种操作 提取出来放到某一个 Visitor对象中去。虽然有时候这些操作直接放到 子类中也还好,但是会让各个子类的方法膨胀(想象每多一种操作就要多一个基类方法,所有子类就要跟着添加实现)

相关类基本接口如下

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
struct Visitor;// forward declaration
struct Obj{
virtual void Accept(Visitor *v);
};

struct OA;// forward declaration also
struct OB;
struct OB;

struct Visitor{
virtual void AcceptOA(OA*o);
virtual void AcceptOB(OB*o);
virtual void AcceptOC(OC*o);
// may OD OE
};

struct OA:public Obj{
void Accept(Visitor *v)override{
v->AcceptOA(this);
}
};
struct OB:public Obj{
void Accept(Visitor *v)override{
v->AcceptOB(this);
}
};
struct OC:public Obj{
void Accept(Visitor *v)override{
v->AcceptOC(this);
}
};

这里有一种很简单的 Visitor

1
2
3
4
5
struct PrintNameVisitor{
virtual void AcceptOA(OA*o)override{ printf("PrintNameVisitor::AcceptOA(%x))\n",o); }
virtual void AcceptOB(OB*o)override{ printf("PrintNameVisitor::AcceptOB(%x))\n",o); }
virtual void AcceptOC(OC*o)override{ printf("PrintNameVisitor::AcceptOC(%x))\n",o); }
}

其实无论用与不用visitor收集操作到具体的visitor 类,实际上需要添加的方法都是一样多的,区别只在于 位置。用了visitor之后就把这种行为统一放到了 Visitor子类中。另外要注意,Visitor作为外部类,访问具体元素时只能访问公共接口,有时候会添加一些复杂度。
归根到底Visitor是一种方法实现位置的选择,

关于避免元素类的 方法数目膨胀(以及由此带来的 同类visitor行为的代码分散)不再赘述。

双分派

多分派的意义见 wiki 多分派
x分派的意思是 运行时方法调用依赖于x个参数动态路由到某个具体实现
C++的虚函数就是一种单分派,因为一次虚函数的调用,依赖于this指针的vfptr,然后查一次表,最后找到子类实现。虚函数调用过程依赖于一个参数 (this指针隐式传入), 即是单分派。注意虚函数是一种单分派的实现方式,但不是所有。

Visitor的双分派过程

此处有用户代码

1
2
3
4
struct Visitor* vis = new PrintNameVisitor;
struct Obj o =new ObjA;
// 注意此处 vis 和 o 都是接口
o->Accept(&vis);

语句 o->Accept(&vis); 有两个参数: oa 和 vis.
第一次分派 发生在 o->Accept 的虚函数查表执行,由此进入 OA::Accept(Visitor * vis)

1
2
3
void OA::Accept(Visitor *vis)override{
vis->AcceptOA(this);
}

第二次分派 发生在 vis->AcceptOA(this); 最终进入 PrintNameVisitor::AcceptOA
这就是Visitor的双分派过程。
从此意义上讲,建议Accept改名叫Dispatch

可能的多分派实现

将元素类和Vistor都看作 多分派的参数,由此有

1
2
3
4
5
6
7
8
9
10
11
12
13
// user code
arg0->Dispatch(I0 arg0, I1 arg1, In argn...);
// 第一次分派之后进入 , 得知 arg0 具体类型,调用 I1::DispatchC0
ConcreteArg0::Dispath(I0 arg0,I1 arg1,In argn...){
arg1->DispatchC0(arg0,arg1,argn...);
}
// 第二次分派之后进入,此时再次得知 arg1 具体类型, 调用I1::DispatchC0C1
ConcreteArg1::DispatchC0(arg0,arg1,argn...){
arg2->DispatchC0C1(arg0,arg1,argn...);
}
// 最终进入,这里的所有参数都是具体类型的
ConcreteArgn::DispatchC0C1Cn(arg0,arg1,argn...){
}

Visitor与双分派分析

Obj::Accept(Visitor*) 是一个双分派的入口,相关分派参数是 Obj 和 Visitor。最终分派的目标实现在 Vistor实例中例如 PrintNameVisitor::AcceptOA.

  1. 双分派不影响分派目标的实现。就是说,不管用不用双分派,针对 Obj集合Visitor集合(或者操作集合)的每一种组合都要求有实现,在这里双分派只是给了另一种放置代码的选择(以前针对同Obj的操作都放到Obj中,现在针对同种操作的都放到Visitor中)
  2. 双分派抽象了行为到Visitor对象,由此增加操作行为的调度的代码的复用。比如
    1
    2
    3
    4
    Obj * obj = createObj();
    Visitor * viss[]={new PrintNameVisitor,new EatLaunchVisitor,new BalahVisitor};
    for(auto vis:viss)
    obj.Accept(vis);
  3. 破坏封装。因为visitor作为外部类操作元素类,所以可能会要求元素类公开某些属性

– 杂谈而已,徒然博君一笑

首先要明确的是,就能力来讲,面向对象不比面向过程更强大。新的编程模式只是选择用 一丢丢性能 或者其他换取 易扩展性、健壮性等 东西

试想我们是从面向过程向面向对象转变的程序🦍

面向对象三大特征

  • 封装
  • 继承
  • 多态

封装

封装的意义在于 为具有一定关系的数据集合命名
于是相对于无明确相关意义的零散变量对象 作为一种新的逻辑实体 出现在我们的视野里
后边的所有东西都是围绕着 这俩货 的探讨。

PS:纯粹的封装是没有开销的。所谓封装事实上只是提供了一种 聚合数据的 作用。在引入 其他的需要运行时的时间空间开销的概念进来之前,封装只是最基本的操作。面向过程的C也还有struct呢,c只是对继承和多态不感兴趣罢了。

继承

继承分为两种,接口继承与实现继承。
继承的意义在于 代码复用,但接口继承的代码复用和实现继承的代码复用差距甚远。接口继承允许 接口的使用代码 被复用;实现继承 允许父类的代码被复用。两者可以兼容,但两者兼具时就会造成一定意义上的混淆。
就C++的成员函数而言, 基础普通成员函数就是实现继承,虚函数就是 接口继承+实现继承

实现继承

试想:当只有纯粹的 实现继承发生时,类层次之间的关系更多表现为一种 静态的组合:等于是子类拥有一个父类的实例成员。但是用户还是得把两者的代码区分开

实现继承的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 struct Base{
void what(){};
};
struct Derived:public Base{
void what(){
Base::what();
...;
};
}
int main(){
// Base
Base b;
b.what();

// Der
Derived d;
d.what();
d.Base::what();
}

在用户看来,实现继承在使用上没有任何帮助–不能减少任何代码。(不考虑模版元编程这种只凭名字就能做到复用客户代码的技术)。
所以 实现继承是一种 模块内的技术,只是发生在子类里的代码复用而已。
另一方面,实现继承是 对类相关关系的归纳,是类间 共性的表示,对于厘清类层级有很大帮助。

接口继承

接口继承允许客户代码 复用。

  • 核心:父类接口调用代码的延迟绑定
  • 表现: 子类实例可以平替接口对象

看一个实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct VBase{
virtual void what(){};
};
struct VDerived:public VBase{
virtual void what()override{
VBase::what();
...
}
}

void doWhat(VBase & base){
base.what();
}
int main(){
VBase b;
vDerived d;

doWhat(b);
doWhat(d);
}

VDerived::what() 中 使用了 继承来的 VBase::what(), 这只是举例说明 虚函数的实现也会被继承,实际上更多时候整个接口实现都会被重写,而与基类接口实现无关。

注意:doWhat 就是客户代码,接口继承允许 客户代码重用。

大型系统的开发,可以按是否依赖其他模块,区分为两部分:

  1. 未依赖其他模块的代码可以看作纯粹的子模块,这一部分代码不涉及系统的子模块之间的依赖,考量时的上下文可以缩减到子模块本身,于是复杂度降低。
  2. 涉及到子模块交互的代码。子模块之间的依赖的存在,就意味着客户的存在,由于子模块之间的复杂性(双向依赖),客户之间的关系错综复杂. 减少这类依赖关系带来的开发复杂性,就是接口继承的意义。 所以有种约束叫做:“规定子模块之间只能通过接口交互”

多态

泛泛而论,多态指的是 代码运行时的多种可能,对C++而言,多态就是 延迟绑定使能的 接口的运行时查表执行。
多态是与接口继承息息相关的。