一键导入
ue-trefcountptr-member-needs-complete-type
A TRefCountPtr/TSharedPtr member in a UE class needs the pointee's full definition, not a forward decl.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
A TRefCountPtr/TSharedPtr member in a UE class needs the pointee's full definition, not a forward decl.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
When WSL's mirrored networking fails and falls back to "None", plus /etc/wsl.conf has generateResolvConf=false, the distro has no DNS; fix both layers.
When a Windows shell (PowerShell/cmd) feeds a bash script into WSL, CRLF line endings can corrupt the first shell builtin; force LF or pipe via a temp file.
Before "fixing" a recurring error, check git log to see if it was already fixed upstream — the working tree may just be stale.
Always add a space after URL brackets in Markdown to prevent 404 errors with special characters.
Plan a Python 3 modernization sweep (f-strings, super(), type hints) as a series of mechanical PRs, not one mega-PR.
Run a quick non-behavioral audit of a Python repo and split findings into bug / dead-code / style PRs.
| name | ue-trefcountptr-member-needs-complete-type |
| description | A TRefCountPtr/TSharedPtr member in a UE class needs the pointee's full definition, not a forward decl. |
| tags | ["unreal","cpp","smart-pointer","forward-declaration","pitfall"] |
TRefCountPtr<T> / TSharedPtr<T> members need T's complete typeYou added a data member like this to a UE module header:
// Pb4ueTrpcServerConnection.h
class FPb4ueTrpcServerStream; // forward decl
class FPb4ueTrpcServerConnection : public TSharedFromThis<...>
{
...
TMap<uint32, TRefCountPtr<FPb4ueTrpcServerStream>> ServerStreams;
};
The header itself parses. Then the first TU that constructs the
enclosing class — typically via MakeShared<FPb4ueTrpcServerConnection>()
or a TUniquePtr<FPb4ueTrpcServerConnection> reset — fails to compile
with errors pointing at the smart pointer's destructor:
error: invalid application of 'sizeof' to an incomplete type 'FPb4ueTrpcServerStream'
note: in instantiation of member function 'TRefCountPtr<FPb4ueTrpcServerStream>::~TRefCountPtr' requested here
note: in instantiation of member function 'TMap<unsigned int, TRefCountPtr<FPb4ueTrpcServerStream>>::~TMap' requested here
note: in instantiation of member function 'FPb4ueTrpcServerConnection::~FPb4ueTrpcServerConnection' requested here
Same pattern with TSharedPtr<T>, TUniquePtr<T>, and TArray<TRefCountPtr<T>>.
TRefCountPtr<T>, TSharedPtr<T>, TUniquePtr<T> all call into T's
destructor from their own destructor. That requires T to be a
complete type at the point the smart pointer's destructor is
instantiated. When the smart pointer is a class member, the enclosing
class's implicit destructor instantiates the member destructors, so the
completeness requirement propagates out to every TU that destroys the
enclosing class — including the one that calls MakeShared<...>().
A forward declaration in the header is enough to parse the member declaration (you're only taking a pointer-sized field), but not enough to destroy it. The compiler doesn't fire at the header; it fires at the first caller that forces instantiation.
This is the same root cause as the classic "pimpl needs out-of-line
destructor" rule, but UE's TRefCountPtr has no std::unique_ptr-style
defaulted-dtor escape hatch, so the workaround is narrower.
Pick one, in order of preference:
Include the pointee's header in the enclosing class's header. Simplest and correct when the pointee is already an internal type of the same module:
// Pb4ueTrpcServerConnection.h
#include "Pb4ueTrpcServerStream.h" // not just a forward decl
class FPb4ueTrpcServerConnection : ...
{
TMap<uint32, TRefCountPtr<FPb4ueTrpcServerStream>> ServerStreams;
};
Move the member into a PImpl. If you genuinely want to keep the
pointee out of the public header (e.g. it drags in a heavy vendor
header), hide the whole state behind an opaque FImpl forward-declared
in the header and defined in the .cpp, and give the enclosing class
an out-of-line destructor in that .cpp:
// Foo.h
class FFoo { TUniquePtr<struct FImpl> Impl; public: ~FFoo(); };
// Foo.cpp
struct FImpl { TRefCountPtr<FHeavy> Heavy; };
FFoo::~FFoo() = default; // instantiated here, where FImpl is complete
Store a raw FHeavy* and manage lifetime by hand. Only if (1)
and (2) don't fit. You lose the ref-count/ownership guarantees that
made you reach for TRefCountPtr in the first place, so this is
rarely the right answer in UE code.
Do not try to fix it by adding = default to the enclosing class's
destructor in the header — that's still an inline definition and still
requires the complete type at every use site. The fix is either
"include the header" or "move the dtor out of line".
Wrong:
// Pb4ueTrpcServerConnection.h
class FPb4ueTrpcServerStream; // forward-declared only
class FPb4ueTrpcServerConnection : public TSharedFromThis<FPb4ueTrpcServerConnection, ESPMode::ThreadSafe>
{
TMap<uint32, TRefCountPtr<FPb4ueTrpcServerStream>> ServerStreams;
};
// SomeOther.cpp
auto C = MakeShared<FPb4ueTrpcServerConnection>(); // <-- fails here
Right:
// Pb4ueTrpcServerConnection.h
#include "Pb4ueTrpcServerStream.h"
class FPb4ueTrpcServerConnection : public TSharedFromThis<FPb4ueTrpcServerConnection, ESPMode::ThreadSafe>
{
TMap<uint32, TRefCountPtr<FPb4ueTrpcServerStream>> ServerStreams;
};
TRefCountPtr" and start swapping smart pointer types. Swap the
include, not the smart pointer.TArray<TRefCountPtr<T>>, TSet<TSharedPtr<T>>,
TMap<K, TUniquePtr<T>>, etc. The container's destructor instantiates
the element's destructor, which instantiates the smart pointer's.TWeakPtr<T> and raw T* do not require a complete type, because
they don't call T's destructor. If you really only need an observer
handle in the header, downgrade to one of those.std::unique_ptr pimpl-dtor rule
(https://en.cppreference.com/w/cpp/memory/unique_ptr) — same root
cause, UE-flavored.