From 063ee8336067420c195412c04b886e696051d14f Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Thu, 5 Mar 2026 13:28:28 -0800 Subject: [PATCH 1/4] Add C.13 to ensure data member lifetimes nest when needed... see #2316 Closes #2316 --- CppCoreGuidelines.md | 97 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/CppCoreGuidelines.md b/CppCoreGuidelines.md index 96382d7fc..79c810010 100644 --- a/CppCoreGuidelines.md +++ b/CppCoreGuidelines.md @@ -4671,6 +4671,7 @@ Concrete type rule summary: * [C.10: Prefer concrete types over class hierarchies](#rc-concrete) * [C.11: Make concrete types regular](#rc-regular) * [C.12: Don't make data members `const` or references in a copyable or movable type](#rc-constref) +* [C.13: If one data member uses another, declare it before the other](#rc-lifetime) ### C.10: Prefer concrete types over class hierarchies @@ -4792,6 +4793,102 @@ If you need a member to point to something, use a pointer (raw or smart, and `gs Flag a data member that is `const`, `&`, or `&&` in a type that has any copy or move operation. +### C.13: If one data member uses another, declare it before the other + +##### Reason + +Data members are initialized in the order they are declared, and destroyed in the reverse order. + +##### Discussion + +If data member `B` uses another data member `A`, then `A` must be declared before `B` so that `A` outlives `B`, meaning that `A`'s lifetime starts before and ends after `B`'s lifetime. Otherwise, during construction and destruction `B` will attempt to use `A` outside its lifetime. + +##### Example; bad + + // Bad: b uses a, but a is declared after b. + // Construction order is b then a; destruction order is a then b. + // So b touches a outside a's lifetime. + + class X { + struct B { + string* p; + explicit B(string& a) : p(&a) {} + ~B() { cout << *p; } // uses a (via p) + }; + + B b; // constructed first + string a = "some heap allocated string value"; // constructed after b; destroyed before b + + public: + X() : b(a) {} // uses a before it is constructed -> use-before-alloc UB + ~X() = default; // accesses a after it is destroyed -> use-after-free UB + }; + +##### Example; good + + // Corrected: Just declare a before b + + class X { + struct B { + string* p; + explicit B(string& a) : p(&a) {} + ~B() { cout << *p; } // uses a (via p) + }; + + string a = "some heap allocated string value"; // constructed before b; destroyed after b + B b; // constructed second + + public: + X() : b(a) {} // ok + ~X() = default; // ok + }; + +##### Example; bad + +This can also come up with concurrency. Ensure that an async operation that access a value is joined before the value it accesses is destroyed. + + class X { + public: + X() + : a(std::make_unique(12)) + { + b = std::make_unique( + [this]{ + std::this_thread::sleep_for(std::chrono::seconds(1)); + std::cout << "Value: " << *a << std::endl; + }); + } + + std::unique_ptr b; + std::unique_ptr a; + }; + +##### Example; good + +This can also come up with concurrency. Ensure that an async operation that access a value is joined before the value it accesses is destroyed. + + // Corrected: Just declare a before b + + class X { + public: + X() + : a(std::make_unique(12)) + { + b = std::make_unique( + [this]{ + std::this_thread::sleep_for(std::chrono::seconds(1)); + std::cout << "Value: " << *a << std::endl; + }); + } + + std::unique_ptr a; + std::unique_ptr b; + }; + +##### Enforcement + +* Flag a member initializer that refers to an object before it is constructed. + ## C.ctor: Constructors, assignments, and destructors From 0de977137fae3e4bd3600de6bd54c468b7a4a168 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Thu, 16 Apr 2026 11:16:12 -0700 Subject: [PATCH 2/4] Apply suggestion from @beinhaerter Co-authored-by: Werner Henze <34543625+beinhaerter@users.noreply.github.com> --- CppCoreGuidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CppCoreGuidelines.md b/CppCoreGuidelines.md index 79c810010..428d8c784 100644 --- a/CppCoreGuidelines.md +++ b/CppCoreGuidelines.md @@ -4872,7 +4872,7 @@ This can also come up with concurrency. Ensure that an async operation that acce class X { public: X() - : a(std::make_unique(12)) + : a{std::make_unique(12)} { b = std::make_unique( [this]{ From 0ece1d9aa0c6059e6af87e5afe639baecfbf0b67 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Thu, 16 Apr 2026 11:16:49 -0700 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: Werner Henze <34543625+beinhaerter@users.noreply.github.com> --- CppCoreGuidelines.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CppCoreGuidelines.md b/CppCoreGuidelines.md index 428d8c784..d1f27edc5 100644 --- a/CppCoreGuidelines.md +++ b/CppCoreGuidelines.md @@ -4812,7 +4812,7 @@ If data member `B` uses another data member `A`, then `A` must be declared befor class X { struct B { string* p; - explicit B(string& a) : p(&a) {} + explicit B(string& a) : p{&a} {} ~B() { cout << *p; } // uses a (via p) }; @@ -4820,7 +4820,7 @@ If data member `B` uses another data member `A`, then `A` must be declared befor string a = "some heap allocated string value"; // constructed after b; destroyed before b public: - X() : b(a) {} // uses a before it is constructed -> use-before-alloc UB + X() : b{a} {} // uses a before it is constructed -> use-before-alloc UB ~X() = default; // accesses a after it is destroyed -> use-after-free UB }; @@ -4831,7 +4831,7 @@ If data member `B` uses another data member `A`, then `A` must be declared befor class X { struct B { string* p; - explicit B(string& a) : p(&a) {} + explicit B(string& a) : p{&a} {} ~B() { cout << *p; } // uses a (via p) }; @@ -4839,18 +4839,18 @@ If data member `B` uses another data member `A`, then `A` must be declared befor B b; // constructed second public: - X() : b(a) {} // ok + X() : b{a} {} // ok ~X() = default; // ok }; ##### Example; bad -This can also come up with concurrency. Ensure that an async operation that access a value is joined before the value it accesses is destroyed. +This can also come up with concurrency. Ensure that an async operation that accesses a value is joined before the value it accesses is destroyed. class X { public: X() - : a(std::make_unique(12)) + : a{std::make_unique(12)} { b = std::make_unique( [this]{ @@ -4865,7 +4865,7 @@ This can also come up with concurrency. Ensure that an async operation that acce ##### Example; good -This can also come up with concurrency. Ensure that an async operation that access a value is joined before the value it accesses is destroyed. +This can also come up with concurrency. Ensure that an async operation that accesses a value is joined before the value it accesses is destroyed. // Corrected: Just declare a before b From 4bbd68cc8493aacce7975a728385449fd0c3e272 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Thu, 16 Apr 2026 12:07:20 -0700 Subject: [PATCH 4/4] Apply wording and dictionary fixes --- CppCoreGuidelines.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CppCoreGuidelines.md b/CppCoreGuidelines.md index d1f27edc5..9469cdb59 100644 --- a/CppCoreGuidelines.md +++ b/CppCoreGuidelines.md @@ -4671,7 +4671,7 @@ Concrete type rule summary: * [C.10: Prefer concrete types over class hierarchies](#rc-concrete) * [C.11: Make concrete types regular](#rc-regular) * [C.12: Don't make data members `const` or references in a copyable or movable type](#rc-constref) -* [C.13: If one data member uses another, declare it before the other](#rc-lifetime) +* [C.13: If data member `B` uses another data member `A`, declare `A` before `B`](#rc-lifetime) ### C.10: Prefer concrete types over class hierarchies @@ -4793,7 +4793,7 @@ If you need a member to point to something, use a pointer (raw or smart, and `gs Flag a data member that is `const`, `&`, or `&&` in a type that has any copy or move operation. -### C.13: If one data member uses another, declare it before the other +### C.13: If data member `B` uses another data member `A`, declare `A` before `B` ##### Reason @@ -4807,7 +4807,7 @@ If data member `B` uses another data member `A`, then `A` must be declared befor // Bad: b uses a, but a is declared after b. // Construction order is b then a; destruction order is a then b. - // So b touches a outside a's lifetime. + // So b touches a outside the lifetime of a. class X { struct B {