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
diff --git a/AGENTS.md b/AGENTS.md
index 8d73f2c..5be9cad 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -14,7 +14,7 @@
 - **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**: `git@github.com:apache/fory.git` is remote repository, do not use other remote repository when you want to check code under `main` branch.
+- **Remote git repository**: `git@github.com: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 `git@github.com: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 `git@github.com: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 616facb..43b91dd 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 @@
 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>
 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 @@
 ///   - 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 @@
   // 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 @@
                     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 @@
   // 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 @@
       (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 @@
 
 /// 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 @@
       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 528800f..c1bf377 100644
--- a/cpp/fory/meta/field_test.cc
+++ b/cpp/fory/meta/field_test.cc
@@ -113,7 +113,7 @@
   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 @@
   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 @@
   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, 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 @@
                 (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, 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 142381e..72ca3ff 100644
--- a/cpp/fory/serialization/field_serializer_test.cc
+++ b/cpp/fory/serialization/field_serializer_test.cc
@@ -656,7 +656,7 @@
 
   // 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 31fe1c0..5b92f8c 100644
--- a/cpp/fory/serialization/struct_serializer.h
+++ b/cpp/fory/serialization/struct_serializer.h
@@ -604,7 +604,7 @@
   }
 
   /// 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 @@
       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 6706656..40f26d2 100644
--- a/cpp/fory/serialization/type_resolver.h
+++ b/cpp/fory/serialization/type_resolver.h
@@ -511,7 +511,9 @@
   } 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 7469f7b..0f78e1a 100644
--- a/docs/guide/cpp/field-configuration.md
+++ b/docs/guide/cpp/field-configuration.md
@@ -103,7 +103,7 @@
 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 @@
 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 @@
 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 @@
 
 ### 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 @@
 
 ## 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 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 @@
 
 ### 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 3963852..ab191a9 100644
--- a/docs/guide/cpp/supported-types.md
+++ b/docs/guide/cpp/supported-types.md
@@ -140,7 +140,7 @@
 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 b4d803a..729f244 100644
--- a/docs/guide/xlang/field-reference-tracking.md
+++ b/docs/guide/xlang/field-reference-tracking.md
@@ -113,13 +113,13 @@
 
 ### 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 @@
 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