This is an automated email from the ASF dual-hosted git repository.

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new 9b2a2cea0 feat(c++): make shared_ptr track ref by default (#3214)
9b2a2cea0 is described below

commit 9b2a2cea0419923e45d0730f974d0add06e5a36d
Author: Shawn Yang <[email protected]>
AuthorDate: Tue Jan 27 11:02:12 2026 +0800

    feat(c++): make shared_ptr track ref by default (#3214)
    
    ## Why?
    
    Make C++ shared ownership fields track references by default and extend
    ref tracking semantics to SharedWeak to match intended ownership
    behavior and reduce required annotations.
    
    ## What does this PR do?
    
    - Make `std::shared_ptr<T>` (and `fory::serialization::SharedWeak<T>`)
    track refs by default in field metadata and serializers.
    - Extend smart-pointer detection and option validation to include
    `SharedWeak<T>` alongside `shared_ptr`/`unique_ptr`.
    - Update compile-time helpers, resolver logic, and tests to reflect the
    new default tracking behavior.
    - Refresh C++ and xlang docs to document defaults and valid options for
    `SharedWeak`.
    
    ## Related issues
    
    #2906
    
    ## Does this PR introduce any user-facing change?
    
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
---
 AGENTS.md                                       |  2 +-
 cpp/fory/meta/field.h                           | 73 +++++++++++++++++--------
 cpp/fory/meta/field_test.cc                     | 18 +++---
 cpp/fory/serialization/field_serializer_test.cc |  2 +-
 cpp/fory/serialization/struct_serializer.h      |  6 +-
 cpp/fory/serialization/type_resolver.h          |  4 +-
 docs/guide/cpp/field-configuration.md           | 56 ++++++++++---------
 docs/guide/cpp/supported-types.md               |  2 +-
 docs/guide/xlang/field-reference-tracking.md    | 29 +++++-----
 9 files changed, 111 insertions(+), 81 deletions(-)

diff --git a/AGENTS.md b/AGENTS.md
index 8d73f2c7f..5be9cad68 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -14,7 +14,7 @@ While working on Fory, please remember:
 - **Cross-Language Consistency**: Maintain consistency across language 
implementations while respecting language-specific idioms.
 - **GraalVM support using fory codegen**: For GraalVM, use `fory codegen` to 
generate the serializer when building a native image. Do not use GraalVM 
reflect-related configuration unless for JDK `proxy`.
 - **Xlang Type System**: Java `native mode(xlang=false)` shares same type 
systems between type id from `Types.BOOL~Types.STRING` with `xlang 
mode(xlang=true)`, but for other types, java `native mode` has different type 
ids.
-- **Remote git repository**: `[email protected]:apache/fory.git` is remote 
repository, do not use other remote repository when you want to check code 
under `main` branch.
+- **Remote git repository**: `[email protected]:apache/fory.git` is remote 
repository, do not use other remote repository when you want to check code 
under `main` branch, **`apache/main`** is the only target main branch instead 
of `origin/main`
 - **Contributor git repository**: A contributor should fork the 
`[email protected]:apache/fory.git` repo, and git push the code changes into 
their forked repo, then create a pull request from the branch in their forked 
repo into `[email protected]:apache/fory.git`.
 - **Debug Test Errors**: always set environment variable 
`ENABLE_FORY_DEBUG_OUTPUT` to `1` to see debug output.
 
diff --git a/cpp/fory/meta/field.h b/cpp/fory/meta/field.h
index 616facbff..43b91dd99 100644
--- a/cpp/fory/meta/field.h
+++ b/cpp/fory/meta/field.h
@@ -33,23 +33,28 @@
 
 namespace fory {
 
+namespace serialization {
+template <typename T> class SharedWeak;
+} // namespace serialization
+
 // ============================================================================
 // Field Option Tags
 // ============================================================================
 
-/// Tag to mark a shared_ptr/unique_ptr field as nullable.
-/// Only valid for std::shared_ptr and std::unique_ptr types.
+/// Tag to mark a shared_ptr/SharedWeak/unique_ptr field as nullable.
+/// Only valid for std::shared_ptr, SharedWeak, and std::unique_ptr types.
 /// For nullable primitives/strings, use std::optional<T> instead.
 struct nullable {};
 
 /// Tag to explicitly mark a pointer field as non-nullable.
 /// Useful for future pointer types (e.g., weak_ptr) that might be nullable by
-/// default. For shared_ptr/unique_ptr, non-nullable is already the default.
+/// default. For shared_ptr/SharedWeak/unique_ptr, non-nullable is already the
+/// default.
 struct not_null {};
 
-/// Tag to enable reference tracking for shared_ptr fields.
-/// Only valid for std::shared_ptr types (requires shared ownership for ref
-/// tracking).
+/// Tag to enable reference tracking for shared_ptr/SharedWeak fields.
+/// Only valid for std::shared_ptr or SharedWeak types (requires shared
+/// ownership for ref tracking).
 struct ref {};
 
 /// Template tag to control dynamic type dispatch for smart pointer fields.
@@ -108,6 +113,14 @@ struct is_shared_ptr<std::shared_ptr<T>> : std::true_type 
{};
 template <typename T>
 inline constexpr bool is_shared_ptr_v = is_shared_ptr<T>::value;
 
+template <typename T> struct is_shared_weak : std::false_type {};
+
+template <typename T>
+struct is_shared_weak<serialization::SharedWeak<T>> : std::true_type {};
+
+template <typename T>
+inline constexpr bool is_shared_weak_v = is_shared_weak<T>::value;
+
 template <typename T> struct is_unique_ptr : std::false_type {};
 
 template <typename T, typename D>
@@ -123,9 +136,10 @@ template <typename T> struct is_optional<std::optional<T>> 
: std::true_type {};
 template <typename T>
 inline constexpr bool is_optional_v = is_optional<T>::value;
 
-/// Helper to check if type is shared_ptr or unique_ptr
+/// Helper to check if type is shared_ptr/SharedWeak or unique_ptr
 template <typename T>
-inline constexpr bool is_smart_ptr_v = is_shared_ptr_v<T> || 
is_unique_ptr_v<T>;
+inline constexpr bool is_smart_ptr_v =
+    is_shared_ptr_v<T> || is_shared_weak_v<T> || is_unique_ptr_v<T>;
 
 // ============================================================================
 // Option Tag Detection
@@ -495,6 +509,7 @@ public:
 ///   - Primitives/strings: No options allowed (use std::optional for nullable)
 ///   - std::optional<T>: Inherently nullable, no options needed
 ///   - std::shared_ptr<T>: Can use nullable and/or ref
+///   - SharedWeak<T>: Can use nullable and/or ref
 ///   - std::unique_ptr<T>: Can use nullable only (no ref - exclusive
 ///   ownership)
 template <typename T, int16_t Id, typename... Options> class field {
@@ -506,7 +521,8 @@ template <typename T, int16_t Id, typename... Options> 
class field {
   // Validate: nullable only for smart pointers
   static_assert(!detail::has_option_v<nullable, Options...> ||
                     detail::is_smart_ptr_v<T>,
-                "fory::nullable is only valid for shared_ptr/unique_ptr. "
+                "fory::nullable is only valid for shared_ptr/SharedWeak/"
+                "unique_ptr. "
                 "Use std::optional<T> for nullable primitives/strings.");
 
   // Validate: not_null only for smart pointers (for now)
@@ -514,16 +530,17 @@ template <typename T, int16_t Id, typename... Options> 
class field {
                     detail::is_smart_ptr_v<T>,
                 "fory::not_null is only valid for pointer types.");
 
-  // Validate: ref only for shared_ptr
+  // Validate: ref only for shared_ptr/SharedWeak
   static_assert(!detail::has_option_v<ref, Options...> ||
-                    detail::is_shared_ptr_v<T>,
-                "fory::ref is only valid for shared_ptr "
+                    detail::is_shared_ptr_v<T> || detail::is_shared_weak_v<T>,
+                "fory::ref is only valid for shared_ptr/SharedWeak "
                 "(reference tracking requires shared ownership).");
 
   // Validate: dynamic<V> only for smart pointers
   static_assert(!detail::has_dynamic_option_v<Options...> ||
                     detail::is_smart_ptr_v<T>,
-                "fory::dynamic<V> is only valid for shared_ptr/unique_ptr.");
+                "fory::dynamic<V> is only valid for shared_ptr/SharedWeak/"
+                "unique_ptr.");
 
   // Validate: no options for optional (inherently nullable)
   static_assert(!detail::is_optional_v<T> || sizeof...(Options) == 0,
@@ -532,7 +549,8 @@ template <typename T, int16_t Id, typename... Options> 
class field {
   // Validate: no options for non-smart-pointer types
   static_assert(detail::is_smart_ptr_v<T> || detail::is_optional_v<T> ||
                     sizeof...(Options) == 0,
-                "Options are only valid for shared_ptr/unique_ptr fields. "
+                "Options are only valid for shared_ptr/SharedWeak/unique_ptr "
+                "fields. "
                 "Use std::optional<T> for nullable primitives/strings.");
 
 public:
@@ -547,9 +565,11 @@ public:
       (detail::is_smart_ptr_v<T> && detail::has_option_v<nullable, 
Options...>);
 
   /// Reference tracking is enabled if:
-  /// - It's std::shared_ptr with fory::ref option
-  static constexpr bool track_ref =
-      detail::is_shared_ptr_v<T> && detail::has_option_v<ref, Options...>;
+  /// - It's std::shared_ptr or SharedWeak (default)
+  /// - Or explicitly marked with fory::ref
+  static constexpr bool track_ref = detail::is_shared_ptr_v<T> ||
+                                    detail::is_shared_weak_v<T> ||
+                                    detail::has_option_v<ref, Options...>;
 
   /// Dynamic type dispatch control:
   /// - -1 (AUTO): Use std::is_polymorphic<T> to decide
@@ -666,7 +686,8 @@ inline constexpr bool field_is_nullable_v = 
field_is_nullable<T>::value;
 
 /// Get track_ref from field type
 template <typename T> struct field_track_ref {
-  static constexpr bool value = false;
+  static constexpr bool value =
+      detail::is_shared_ptr_v<T> || detail::is_shared_weak_v<T>;
 };
 
 template <typename T, int16_t Id, typename... Options>
@@ -705,21 +726,25 @@ struct ParseFieldTagEntry {
       is_optional_v<FieldType> ||
       (is_smart_ptr_v<FieldType> && has_option_v<nullable, Options...>);
 
-  static constexpr bool track_ref =
-      is_shared_ptr_v<FieldType> && has_option_v<ref, Options...>;
+  static constexpr bool track_ref = is_shared_ptr_v<FieldType> ||
+                                    is_shared_weak_v<FieldType> ||
+                                    has_option_v<ref, Options...>;
 
   static constexpr int dynamic_value = get_dynamic_value_v<Options...>;
 
   // Compile-time validation
   static_assert(!has_option_v<nullable, Options...> ||
                     is_smart_ptr_v<FieldType>,
-                "fory::nullable is only valid for shared_ptr/unique_ptr");
+                "fory::nullable is only valid for shared_ptr/SharedWeak/"
+                "unique_ptr");
 
-  static_assert(!has_option_v<ref, Options...> || is_shared_ptr_v<FieldType>,
-                "fory::ref is only valid for shared_ptr");
+  static_assert(!has_option_v<ref, Options...> || is_shared_ptr_v<FieldType> ||
+                    is_shared_weak_v<FieldType>,
+                "fory::ref is only valid for shared_ptr/SharedWeak");
 
   static_assert(!has_dynamic_option_v<Options...> || is_smart_ptr_v<FieldType>,
-                "fory::dynamic<V> is only valid for shared_ptr/unique_ptr");
+                "fory::dynamic<V> is only valid for shared_ptr/SharedWeak/"
+                "unique_ptr");
 
   using type = FieldTagEntry<Id, is_nullable, track_ref, dynamic_value>;
 };
diff --git a/cpp/fory/meta/field_test.cc b/cpp/fory/meta/field_test.cc
index 528800f1b..c1bf37731 100644
--- a/cpp/fory/meta/field_test.cc
+++ b/cpp/fory/meta/field_test.cc
@@ -113,7 +113,7 @@ TEST(Field, SharedPtrNonNullable) {
   using FieldType = field<std::shared_ptr<int32_t>, 3>;
   static_assert(FieldType::tag_id == 3);
   static_assert(FieldType::is_nullable == false);
-  static_assert(FieldType::track_ref == false);
+  static_assert(FieldType::track_ref == true);
 
   FieldType f;
   f = std::make_shared<int32_t>(99);
@@ -126,7 +126,7 @@ TEST(Field, SharedPtrNullable) {
   using FieldType = field<std::shared_ptr<int32_t>, 4, nullable>;
   static_assert(FieldType::tag_id == 4);
   static_assert(FieldType::is_nullable == true);
-  static_assert(FieldType::track_ref == false);
+  static_assert(FieldType::track_ref == true);
 
   FieldType f;
   EXPECT_EQ(f.value, nullptr); // Default is null
@@ -169,7 +169,7 @@ TEST(Field, SharedPtrNotNull) {
   using FieldType = field<std::shared_ptr<int32_t>, 9, not_null>;
   static_assert(FieldType::tag_id == 9);
   static_assert(FieldType::is_nullable == false);
-  static_assert(FieldType::track_ref == false);
+  static_assert(FieldType::track_ref == true);
 }
 
 TEST(Field, SharedPtrNotNullWithRef) {
@@ -222,9 +222,9 @@ TEST(FieldTraits, FieldIsNullable) {
 
 TEST(FieldTraits, FieldTrackRef) {
   static_assert(field_track_ref_v<int> == false);
-  static_assert(field_track_ref_v<std::shared_ptr<int>> == false);
+  static_assert(field_track_ref_v<std::shared_ptr<int>> == true);
   static_assert(field_track_ref_v<field<int, 0>> == false);
-  static_assert(field_track_ref_v<field<std::shared_ptr<int>, 1>> == false);
+  static_assert(field_track_ref_v<field<std::shared_ptr<int>, 1>> == true);
   static_assert(field_track_ref_v<field<std::shared_ptr<int>, 2, ref>> == 
true);
   static_assert(
       field_track_ref_v<field<std::shared_ptr<int>, 3, nullable, ref>> == 
true);
@@ -315,7 +315,7 @@ FORY_FIELD_TAGS(Document, (title, 0),     // string: 
non-nullable
                 (description, 2),         // optional: inherently nullable
                 (author, 3),              // shared_ptr: non-nullable (default)
                 (reviewer, 4, nullable),  // shared_ptr: nullable
-                (parent, 5, ref),         // shared_ptr: non-nullable, with ref
+                (parent, 5, ref),         // shared_ptr: non-nullable, ref
                 (metadata, 6, nullable)); // unique_ptr: nullable
 
 FORY_FIELD_TAGS(Node, (name, 0), (left, 1, nullable, ref),
@@ -371,12 +371,12 @@ TEST(FieldTags, Nullability) {
 }
 
 TEST(FieldTags, RefTracking) {
-  // Only parent has ref tracking
+  // shared_ptr fields track refs by default
   static_assert(detail::GetFieldTagEntry<Document, 0>::track_ref == false);
   static_assert(detail::GetFieldTagEntry<Document, 1>::track_ref == false);
   static_assert(detail::GetFieldTagEntry<Document, 2>::track_ref == false);
-  static_assert(detail::GetFieldTagEntry<Document, 3>::track_ref == false);
-  static_assert(detail::GetFieldTagEntry<Document, 4>::track_ref == false);
+  static_assert(detail::GetFieldTagEntry<Document, 3>::track_ref == true);
+  static_assert(detail::GetFieldTagEntry<Document, 4>::track_ref == true);
   static_assert(detail::GetFieldTagEntry<Document, 5>::track_ref == true);
   static_assert(detail::GetFieldTagEntry<Document, 6>::track_ref == false);
 }
diff --git a/cpp/fory/serialization/field_serializer_test.cc 
b/cpp/fory/serialization/field_serializer_test.cc
index 142381e2f..72ca3ff68 100644
--- a/cpp/fory/serialization/field_serializer_test.cc
+++ b/cpp/fory/serialization/field_serializer_test.cc
@@ -656,7 +656,7 @@ TEST(FieldSerializerTest, FieldMetadataCompileTime) {
 
   // Ref tracking
   static_assert(decltype(FieldRefTrackingHolder::first)::track_ref);
-  static_assert(!decltype(FieldSharedPtrHolder::value)::track_ref);
+  static_assert(decltype(FieldSharedPtrHolder::value)::track_ref);
 
   // not_null doesn't change is_nullable for already non-nullable
   static_assert(!decltype(FieldNotNullRefHolder::node)::is_nullable);
diff --git a/cpp/fory/serialization/struct_serializer.h 
b/cpp/fory/serialization/struct_serializer.h
index 31fe1c0d3..5b92f8cea 100644
--- a/cpp/fory/serialization/struct_serializer.h
+++ b/cpp/fory/serialization/struct_serializer.h
@@ -604,7 +604,7 @@ template <typename T> struct CompileTimeFieldHelpers {
   }
 
   /// Returns true if reference tracking is enabled for the field at Index.
-  /// Only valid for std::shared_ptr fields with fory::ref tag.
+  /// Defaults to true for std::shared_ptr/SharedWeak fields.
   template <size_t Index> static constexpr bool field_track_ref() {
     if constexpr (FieldCount == 0) {
       return false;
@@ -620,9 +620,9 @@ template <typename T> struct CompileTimeFieldHelpers {
       else if constexpr (::fory::detail::has_field_tags_v<T>) {
         return ::fory::detail::GetFieldTagEntry<T, Index>::track_ref;
       }
-      // Default: no reference tracking
+      // Default: shared_ptr/SharedWeak track refs
       else {
-        return false;
+        return field_track_ref_v<RawFieldType>;
       }
     }
   }
diff --git a/cpp/fory/serialization/type_resolver.h 
b/cpp/fory/serialization/type_resolver.h
index 670665607..40f26d2ae 100644
--- a/cpp/fory/serialization/type_resolver.h
+++ b/cpp/fory/serialization/type_resolver.h
@@ -511,7 +511,9 @@ constexpr bool compute_track_ref() {
   } else if constexpr (::fory::detail::has_field_tags_v<T>) {
     return ::fory::detail::GetFieldTagEntry<T, Index>::track_ref;
   } else {
-    return false;
+    using UnwrappedFieldType = fory::unwrap_field_t<ActualFieldType>;
+    return ::fory::detail::is_shared_ptr_v<UnwrappedFieldType> ||
+           ::fory::detail::is_shared_weak_v<UnwrappedFieldType>;
   }
 }
 
diff --git a/docs/guide/cpp/field-configuration.md 
b/docs/guide/cpp/field-configuration.md
index 7469f7bf1..0f78e1ad3 100644
--- a/docs/guide/cpp/field-configuration.md
+++ b/docs/guide/cpp/field-configuration.md
@@ -103,7 +103,7 @@ struct Node {
 FORY_STRUCT(Node, name, next);
 ```
 
-**Valid for:** `std::shared_ptr<T>`, `std::unique_ptr<T>`
+**Valid for:** `std::shared_ptr<T>`, `fory::serialization::SharedWeak<T>`, 
`std::unique_ptr<T>`
 
 **Note:** For nullable primitives or strings, use `std::optional<T>` instead:
 
@@ -123,7 +123,7 @@ Explicitly marks a pointer field as non-nullable. This is 
the default for smart
 fory::field<std::shared_ptr<Data>, 0, fory::not_null> data;  // Must not be 
nullptr
 ```
 
-**Valid for:** `std::shared_ptr<T>`, `std::unique_ptr<T>`
+**Valid for:** `std::shared_ptr<T>`, `fory::serialization::SharedWeak<T>`, 
`std::unique_ptr<T>`
 
 ### fory::ref
 
@@ -138,7 +138,7 @@ struct Graph {
 FORY_STRUCT(Graph, name, left, right);
 ```
 
-**Valid for:** `std::shared_ptr<T>` only (requires shared ownership)
+**Valid for:** `std::shared_ptr<T>`, `fory::serialization::SharedWeak<T>` 
(requires shared ownership)
 
 ### fory::dynamic\<V\>
 
@@ -171,7 +171,7 @@ FORY_STRUCT(Zoo, animal, fixed_animal);
 
 ### Combining Tags
 
-Multiple tags can be combined for shared pointers:
+Multiple tags can be combined for shared pointers and SharedWeak:
 
 ```cpp
 // Nullable + ref tracking
@@ -180,12 +180,13 @@ fory::field<std::shared_ptr<Node>, 0, fory::nullable, 
fory::ref> link;
 
 ## Type Rules
 
-| Type                 | Allowed Options                 | Nullability         
               |
-| -------------------- | ------------------------------- | 
---------------------------------- |
-| Primitives, strings  | None                            | Use 
`std::optional<T>` if nullable |
-| `std::optional<T>`   | None                            | Inherently nullable 
               |
-| `std::shared_ptr<T>` | `nullable`, `ref`, `dynamic<V>` | Non-null by default 
               |
-| `std::unique_ptr<T>` | `nullable`, `dynamic<V>`        | Non-null by default 
               |
+| Type                                 | Allowed Options                 | 
Nullability                        |
+| ------------------------------------ | ------------------------------- | 
---------------------------------- |
+| Primitives, strings                  | None                            | Use 
`std::optional<T>` if nullable |
+| `std::optional<T>`                   | None                            | 
Inherently nullable                |
+| `std::shared_ptr<T>`                 | `nullable`, `ref`, `dynamic<V>` | 
Non-null by default                |
+| `fory::serialization::SharedWeak<T>` | `nullable`, `ref`, `dynamic<V>` | 
Non-null by default                |
+| `std::unique_ptr<T>`                 | `nullable`, `dynamic<V>`        | 
Non-null by default                |
 
 ## Complete Example
 
@@ -510,21 +511,21 @@ FORY_FIELD_CONFIG(DataV2,
 
 ### FORY_FIELD_CONFIG Options Reference
 
-| Method            | Description                                      | Valid 
For                  |
-| ----------------- | ------------------------------------------------ | 
-------------------------- |
-| `.nullable()`     | Mark field as nullable                           | Smart 
pointers, primitives |
-| `.ref()`          | Enable reference tracking                        | 
`std::shared_ptr` only     |
-| `.dynamic(true)`  | Force type info to be written (dynamic dispatch) | Smart 
pointers             |
-| `.dynamic(false)` | Skip type info (use declared type directly)      | Smart 
pointers             |
-| `.varint()`       | Use variable-length encoding                     | 
`uint32_t`, `uint64_t`     |
-| `.fixed()`        | Use fixed-size encoding                          | 
`uint32_t`, `uint64_t`     |
-| `.tagged()`       | Use tagged hybrid encoding                       | 
`uint64_t` only            |
-| `.compress(v)`    | Enable/disable field compression                 | All 
types                  |
+| Method            | Description                                      | Valid 
For                                            |
+| ----------------- | ------------------------------------------------ | 
---------------------------------------------------- |
+| `.nullable()`     | Mark field as nullable                           | Smart 
pointers, primitives                           |
+| `.ref()`          | Enable reference tracking                        | 
`std::shared_ptr`, `fory::serialization::SharedWeak` |
+| `.dynamic(true)`  | Force type info to be written (dynamic dispatch) | Smart 
pointers                                       |
+| `.dynamic(false)` | Skip type info (use declared type directly)      | Smart 
pointers                                       |
+| `.varint()`       | Use variable-length encoding                     | 
`uint32_t`, `uint64_t`                               |
+| `.fixed()`        | Use fixed-size encoding                          | 
`uint32_t`, `uint64_t`                               |
+| `.tagged()`       | Use tagged hybrid encoding                       | 
`uint64_t` only                                      |
+| `.compress(v)`    | Enable/disable field compression                 | All 
types                                            |
 
 ## Default Values
 
 - **Nullable**: Only `std::optional<T>` is nullable by default; all other 
types (including `std::shared_ptr`) are non-nullable
-- **Ref tracking**: Disabled by default for all types (including 
`std::shared_ptr`)
+- **Ref tracking**: Enabled by default for `std::shared_ptr<T>` and 
`fory::serialization::SharedWeak<T>`, disabled for other types unless configured
 
 You **need to configure fields** when:
 
@@ -552,12 +553,13 @@ FORY_FIELD_CONFIG(User,
 
 ### Default Values Summary
 
-| Type                 | Default Nullable | Default Ref Tracking |
-| -------------------- | ---------------- | -------------------- |
-| Primitives, `string` | `false`          | `false`              |
-| `std::optional<T>`   | `true`           | `false`              |
-| `std::shared_ptr<T>` | `false`          | `true`               |
-| `std::unique_ptr<T>` | `false`          | `false`              |
+| Type                                 | Default Nullable | Default Ref 
Tracking |
+| ------------------------------------ | ---------------- | 
-------------------- |
+| Primitives, `string`                 | `false`          | `false`            
  |
+| `std::optional<T>`                   | `true`           | `false`            
  |
+| `std::shared_ptr<T>`                 | `false`          | `true`             
  |
+| `fory::serialization::SharedWeak<T>` | `false`          | `true`             
  |
+| `std::unique_ptr<T>`                 | `false`          | `false`            
  |
 
 ## Related Topics
 
diff --git a/docs/guide/cpp/supported-types.md 
b/docs/guide/cpp/supported-types.md
index 3963852f2..ab191a9ad 100644
--- a/docs/guide/cpp/supported-types.md
+++ b/docs/guide/cpp/supported-types.md
@@ -140,7 +140,7 @@ auto bytes = fory.serialize(shared).value();
 auto decoded = fory.deserialize<std::shared_ptr<Person>>(bytes).value();
 ```
 
-**With reference tracking enabled (`track_ref(true)`):**
+**With reference tracking enabled (default, `track_ref(true)`):**
 
 - Shared objects are serialized once
 - References to the same object are preserved
diff --git a/docs/guide/xlang/field-reference-tracking.md 
b/docs/guide/xlang/field-reference-tracking.md
index b4d803a07..729f24400 100644
--- a/docs/guide/xlang/field-reference-tracking.md
+++ b/docs/guide/xlang/field-reference-tracking.md
@@ -113,13 +113,13 @@ By default, **most fields do not track references** even 
when global `refTrackin
 
 ### Default Behavior by Language
 
-| Language | Default Ref Tracking | Types That Track Refs by Default  |
-| -------- | -------------------- | --------------------------------- |
-| Java     | No                   | None (use annotation to enable)   |
-| Python   | No                   | None (use annotation to enable)   |
-| Go       | No                   | None (use `fory:"ref"` to enable) |
-| C++      | No                   | `std::shared_ptr<T>`              |
-| Rust     | No                   | `Rc<T>`, `Arc<T>`, `Weak<T>`      |
+| Language | Default Ref Tracking | Types That Track Refs by Default           
                |
+| -------- | -------------------- | 
---------------------------------------------------------- |
+| Java     | No                   | None (use annotation to enable)            
                |
+| Python   | No                   | None (use annotation to enable)            
                |
+| Go       | No                   | None (use `fory:"ref"` to enable)          
                |
+| C++      | Yes                  | `std::shared_ptr<T>`, 
`fory::serialization::SharedWeak<T>` |
+| Rust     | No                   | `Rc<T>`, `Arc<T>`, `Weak<T>`               
                |
 
 ### Customizing Per-Field Ref Tracking
 
@@ -146,18 +146,19 @@ public class Document {
 struct Document {
     std::string title;
 
-    // shared_ptr tracks refs by default
+    // shared_ptr/SharedWeak track refs by default
     std::shared_ptr<Author> author;
+    fory::serialization::SharedWeak<Data> data;
 
-    // Explicitly enable ref tracking
-    fory::field<std::vector<Tag>, 1, fory::track_ref<true>> tags;
-
-    // Explicitly disable ref tracking
-    fory::field<std::shared_ptr<Data>, 2, fory::track_ref<false>> data;
+    // Explicitly mark ref tracking when using field wrappers (optional)
+    fory::field<std::shared_ptr<Tag>, 1, fory::ref> tag_owner;
 };
-FORY_STRUCT(Document, title, author, tags, data);
+FORY_STRUCT(Document, title, author, data, tag_owner);
 ```
 
+To disable reference tracking for C++ entirely, set
+`Fory::builder().track_ref(false)` on the serializer.
+
 #### Rust: Field Attributes
 
 ```rust


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to