通过间接示例解释 C++ 可变性
Explain C++ Mutability through Indirection example
在 C++ 编程语言第 4 版的第 16.2.9.4 节“间接可变性”中有一个使用间接而不是 mutable
关键字进行惰性求值的例子的草图。
struct cache {
bool valid;
string rep;
};
class Date {
public:
// ...
string string_rep() const;
private:
cache * c; // initialize in constructor
void compute_cache_value() const;
// ...
};
string Date::string_rep() const {
if (!c->valid) {
compute_cache_value();
c->valid = true;
}
return c->rep;
}
解释不多:
Declaring a member mutable
is most appropriate when only a small part of a representation of small object is allowed to change. More complicated cases are often better handled by placing the changing data in a separate object and accessing it directly.
我正在寻找更完整的解释。特别是,
- 什么是小约束?是内存少还是逻辑少?
- 在构造函数中初始化
c
不会(在不平凡的程度上)打败懒惰吗?也就是说,它确实可以为您提供许多永远不需要的功能。
- 为什么
c
是裸指针而不是像 unique_ptr
这样的东西?前面的章节做了一些努力来演示异常安全和 RAII。
- 如果无论如何都要在构造函数中分配和初始化
c
,为什么不直接使用 mutable cache c
成员呢?
换句话说,这是一个真实的模式还是一个人为的例子来证明间接性与常量性?
"smallness constraint" 并不是真正的约束,只是提示如何编写代码(与使用的内存没有太大关系)。如果你有一个包含 30 个成员的 class,其中 20 个是可变的,那么将它分成两个 class 可能更有意义(一个用于可变部分,一个用于其余部分)。
为什么它不是智能指针:不知道,但可能是书作者太累了:p
为什么它是一个指针:你是对的,没有必要。制作一个没有任何指针的可变缓存对象也可以,如果不需要任何类似指针的东西(比如从外部获取现有对象),指针只会增加另一种制造错误的可能性。
警告:以下文字对战场程序员没有任何实用价值,而不是对程序员-哲学家或普通哲学家。另外,我是 Bjarne Stroustrup 的忠实粉丝,我的观点可能有偏见。不幸的是,Whosebug 格式不适合书籍讨论。
此外,我们正在讨论关于常量可变性的棘手问题,我们应该对编译器和 class 用户撒谎。并且没有单一的正确意见。我已准备好接受评论和投票 ;)
简而言之:
- 你可能没有理解惰性初始化的确切含义(可能是因为书中没有正确选择术语)与 RAII 一样,我们知道 Bjarne 不擅长选择术语 ;)
- 在写一本关于编程的书时,需要做出的决定很少。所以有些问题归结为 "How to write a book?" 而不是 "How to write production code?".
长:
What is the smallness constraint? Is it a small amount of memory or a
small amount of logic?
我将再次引用 Bjarne:
Declaring a member mutable is most appropriate when only a small part of a representation of small object is allowed to change
我认为他在这里的意思是 "small number of data members"。通过将数据成员分组到单独的 class 中进行重构通常是一个很好的建议。 "appropriate" 和 "small" 之间的比率是多少?你自己决定(给定一个真正的问题,一个分析器工具和 memory/speed/battery_life/money/client_happiness 等的约束)
Doesn't initializing c in the constructor defeat (to a nontrivial degree) the laziness? That is, it does work you many never need.
好吧,延迟初始化是指每次用户请求字符串时 非计算 正确的字符串值(即 compute_cache_value()
),但仅在真正需要时。不是用空字符串初始化,对吧? (std::string
在构造时初始化为空字符串)
16.2.9.3 和 16.2.9.4 章 Bjarne 的代码中没有任何构造函数!而且您也不会在代码的构造函数中计算字符串,而是使用空字符串文字对其进行初始化。所有计算都延迟到最后一刻。所以,惰性初始化在这里非常适合我。
作为进一步的过早优化,如果你想要真正的惰性初始化,你可能会离开cache*
指针在构造函数中未初始化,并在第一次 Date::string_rep()
调用时分配。如果您的缓存很大并且用户从不需要它,这将是一堆安全的堆。通过这种方式,您可以将计算包装在 cache
构造函数中,从而将惰性评估呈现为真正的惰性初始化
Why is c a naked pointer instead of something like unique_ptr? The
previous chapters went to a bit of effort to demonstrate exception
safety and RAII.
在"C++ Programming Language, 4th edition"第17章介绍了智能指针,我们正在谈论第16章。另外,描述可变性并不重要,只要你设法[就不会带来任何好处delete
在析构函数中。另一件事是作者会在本章中解释为什么你可以改变 smart_ptr cache
拥有的资源,在 const
方法中只有常量 smart_ptr
对象,这将引入描述运算符重载 (大多数高级 Java 和 Python 程序员会在那个地方扔掉这本书 ;)).
除此之外,这通常是一个难题。首先,Bjarne Stroustrup 的书籍主要被视为教材或指南。那么,在教新人的时候,我们应该先跳到智能指针还是先教原始指针呢?我们应该立即使用标准库还是留到最后几章再使用? C++14 从头开始还是从 "C+" 开始?谁知道?还有一个称为 "over-usage of smart pointers" 的问题,特别是 shared_ptr
.
Why not just have a mutable cache c member if you're going to allocate and initialize c in the constructor anyway?
16.2.9.3中描述的就是这样吧?
并在此处添加一个间接级别是替代解决方案(显示 "there is no universal solutions for all purposes")和这个惊人引用的演示:
All problems in computer science can be solved by another level of indirection, except for the problem of too many layers of indirection.
– David J. Wheeler
没有。正如用户 xan 所阐明的那样,16.9.3 是关于多个可变成员的,而单个 mutable
struct
将提供一些关注点分离。
希望您喜欢阅读!
在 C++ 编程语言第 4 版的第 16.2.9.4 节“间接可变性”中有一个使用间接而不是 mutable
关键字进行惰性求值的例子的草图。
struct cache {
bool valid;
string rep;
};
class Date {
public:
// ...
string string_rep() const;
private:
cache * c; // initialize in constructor
void compute_cache_value() const;
// ...
};
string Date::string_rep() const {
if (!c->valid) {
compute_cache_value();
c->valid = true;
}
return c->rep;
}
解释不多:
Declaring a member
mutable
is most appropriate when only a small part of a representation of small object is allowed to change. More complicated cases are often better handled by placing the changing data in a separate object and accessing it directly.
我正在寻找更完整的解释。特别是,
- 什么是小约束?是内存少还是逻辑少?
- 在构造函数中初始化
c
不会(在不平凡的程度上)打败懒惰吗?也就是说,它确实可以为您提供许多永远不需要的功能。 - 为什么
c
是裸指针而不是像unique_ptr
这样的东西?前面的章节做了一些努力来演示异常安全和 RAII。 - 如果无论如何都要在构造函数中分配和初始化
c
,为什么不直接使用mutable cache c
成员呢?
换句话说,这是一个真实的模式还是一个人为的例子来证明间接性与常量性?
"smallness constraint" 并不是真正的约束,只是提示如何编写代码(与使用的内存没有太大关系)。如果你有一个包含 30 个成员的 class,其中 20 个是可变的,那么将它分成两个 class 可能更有意义(一个用于可变部分,一个用于其余部分)。
为什么它不是智能指针:不知道,但可能是书作者太累了:p
为什么它是一个指针:你是对的,没有必要。制作一个没有任何指针的可变缓存对象也可以,如果不需要任何类似指针的东西(比如从外部获取现有对象),指针只会增加另一种制造错误的可能性。
警告:以下文字对战场程序员没有任何实用价值,而不是对程序员-哲学家或普通哲学家。另外,我是 Bjarne Stroustrup 的忠实粉丝,我的观点可能有偏见。不幸的是,Whosebug 格式不适合书籍讨论。
此外,我们正在讨论关于常量可变性的棘手问题,我们应该对编译器和 class 用户撒谎。并且没有单一的正确意见。我已准备好接受评论和投票 ;)
简而言之:
- 你可能没有理解惰性初始化的确切含义(可能是因为书中没有正确选择术语)与 RAII 一样,我们知道 Bjarne 不擅长选择术语 ;)
- 在写一本关于编程的书时,需要做出的决定很少。所以有些问题归结为 "How to write a book?" 而不是 "How to write production code?".
长:
What is the smallness constraint? Is it a small amount of memory or a small amount of logic?
我将再次引用 Bjarne:
Declaring a member mutable is most appropriate when only a small part of a representation of small object is allowed to change
我认为他在这里的意思是 "small number of data members"。通过将数据成员分组到单独的 class 中进行重构通常是一个很好的建议。 "appropriate" 和 "small" 之间的比率是多少?你自己决定(给定一个真正的问题,一个分析器工具和 memory/speed/battery_life/money/client_happiness 等的约束)
Doesn't initializing c in the constructor defeat (to a nontrivial degree) the laziness? That is, it does work you many never need.
好吧,延迟初始化是指每次用户请求字符串时 非计算 正确的字符串值(即
compute_cache_value()
),但仅在真正需要时。不是用空字符串初始化,对吧? (std::string
在构造时初始化为空字符串)16.2.9.3 和 16.2.9.4 章 Bjarne 的代码中没有任何构造函数!而且您也不会在代码的构造函数中计算字符串,而是使用空字符串文字对其进行初始化。所有计算都延迟到最后一刻。所以,惰性初始化在这里非常适合我。
作为进一步的
过早优化,如果你想要真正的惰性初始化,你可能会离开cache*
指针在构造函数中未初始化,并在第一次Date::string_rep()
调用时分配。如果您的缓存很大并且用户从不需要它,这将是一堆安全的堆。通过这种方式,您可以将计算包装在cache
构造函数中,从而将惰性评估呈现为真正的惰性初始化Why is c a naked pointer instead of something like unique_ptr? The previous chapters went to a bit of effort to demonstrate exception safety and RAII.
在"C++ Programming Language, 4th edition"第17章介绍了智能指针,我们正在谈论第16章。另外,描述可变性并不重要,只要你设法[就不会带来任何好处
delete
在析构函数中。另一件事是作者会在本章中解释为什么你可以改变smart_ptr cache
拥有的资源,在const
方法中只有常量smart_ptr
对象,这将引入描述运算符重载 (大多数高级Java 和 Python程序员会在那个地方扔掉这本书 ;)).除此之外,这通常是一个难题。首先,Bjarne Stroustrup 的书籍主要被视为教材或指南。那么,在教新人的时候,我们应该先跳到智能指针还是先教原始指针呢?我们应该立即使用标准库还是留到最后几章再使用? C++14 从头开始还是从 "C+" 开始?谁知道?还有一个称为 "over-usage of smart pointers" 的问题,特别是
shared_ptr
.Why not just have a mutable cache c member if you're going to allocate and initialize c in the constructor anyway?
16.2.9.3中描述的就是这样吧?
并在此处添加一个间接级别是替代解决方案(显示 "there is no universal solutions for all purposes")和这个惊人引用的演示:All problems in computer science can be solved by another level of indirection, except for the problem of too many layers of indirection. – David J. Wheeler
没有。正如用户 xan 所阐明的那样,16.9.3 是关于多个可变成员的,而单个
mutable
struct
将提供一些关注点分离。
希望您喜欢阅读!