Skip to content

Commit 3166c4b

Browse files
hsutterbeinhaerter
andauthored
Add C.13 to ensure data member lifetimes nest when needed... see #2316 (#2321)
* Add C.13 to ensure data member lifetimes nest when needed... see #2316 Closes #2316 * Apply suggestion from @beinhaerter Co-authored-by: Werner Henze <34543625+beinhaerter@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Werner Henze <34543625+beinhaerter@users.noreply.github.com> * Apply wording and dictionary fixes --------- Co-authored-by: Werner Henze <34543625+beinhaerter@users.noreply.github.com>
1 parent 1414c19 commit 3166c4b

1 file changed

Lines changed: 97 additions & 0 deletions

File tree

CppCoreGuidelines.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4672,6 +4672,7 @@ Concrete type rule summary:
46724672
* [C.10: Prefer concrete types over class hierarchies](#rc-concrete)
46734673
* [C.11: Make concrete types regular](#rc-regular)
46744674
* [C.12: Don't make data members `const` or references in a copyable or movable type](#rc-constref)
4675+
* [C.13: If data member `B` uses another data member `A`, declare `A` before `B`](#rc-lifetime)
46754676

46764677

46774678
### <a name="rc-concrete"></a>C.10: Prefer concrete types over class hierarchies
@@ -4793,6 +4794,102 @@ If you need a member to point to something, use a pointer (raw or smart, and `gs
47934794
Flag a data member that is `const`, `&`, or `&&` in a type that has any copy or move operation.
47944795

47954796

4797+
### <a name="rc-lifetime"></a>C.13: If data member `B` uses another data member `A`, declare `A` before `B`
4798+
4799+
##### Reason
4800+
4801+
Data members are initialized in the order they are declared, and destroyed in the reverse order.
4802+
4803+
##### Discussion
4804+
4805+
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.
4806+
4807+
##### Example; bad
4808+
4809+
// Bad: b uses a, but a is declared after b.
4810+
// Construction order is b then a; destruction order is a then b.
4811+
// So b touches a outside the lifetime of a.
4812+
4813+
class X {
4814+
struct B {
4815+
string* p;
4816+
explicit B(string& a) : p{&a} {}
4817+
~B() { cout << *p; } // uses a (via p)
4818+
};
4819+
4820+
B b; // constructed first
4821+
string a = "some heap allocated string value"; // constructed after b; destroyed before b
4822+
4823+
public:
4824+
X() : b{a} {} // uses a before it is constructed -> use-before-alloc UB
4825+
~X() = default; // accesses a after it is destroyed -> use-after-free UB
4826+
};
4827+
4828+
##### Example; good
4829+
4830+
// Corrected: Just declare a before b
4831+
4832+
class X {
4833+
struct B {
4834+
string* p;
4835+
explicit B(string& a) : p{&a} {}
4836+
~B() { cout << *p; } // uses a (via p)
4837+
};
4838+
4839+
string a = "some heap allocated string value"; // constructed before b; destroyed after b
4840+
B b; // constructed second
4841+
4842+
public:
4843+
X() : b{a} {} // ok
4844+
~X() = default; // ok
4845+
};
4846+
4847+
##### Example; bad
4848+
4849+
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.
4850+
4851+
class X {
4852+
public:
4853+
X()
4854+
: a{std::make_unique<int>(12)}
4855+
{
4856+
b = std::make_unique<std::jthread>(
4857+
[this]{
4858+
std::this_thread::sleep_for(std::chrono::seconds(1));
4859+
std::cout << "Value: " << *a << std::endl;
4860+
});
4861+
}
4862+
4863+
std::unique_ptr<std::jthread> b;
4864+
std::unique_ptr<int> a;
4865+
};
4866+
4867+
##### Example; good
4868+
4869+
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.
4870+
4871+
// Corrected: Just declare a before b
4872+
4873+
class X {
4874+
public:
4875+
X()
4876+
: a{std::make_unique<int>(12)}
4877+
{
4878+
b = std::make_unique<std::jthread>(
4879+
[this]{
4880+
std::this_thread::sleep_for(std::chrono::seconds(1));
4881+
std::cout << "Value: " << *a << std::endl;
4882+
});
4883+
}
4884+
4885+
std::unique_ptr<int> a;
4886+
std::unique_ptr<std::jthread> b;
4887+
};
4888+
4889+
##### Enforcement
4890+
4891+
* Flag a member initializer that refers to an object before it is constructed.
4892+
47964893

47974894
## <a name="s-ctor"></a>C.ctor: Constructors, assignments, and destructors
47984895

0 commit comments

Comments
 (0)