在one of Stroustrup's faq question中,他举了以下例子:
template<class Scalar> class complex {
public:
complex() : re(0), im(0) { }
complex(Scalar r) : re(r), im(0) { }
complex(Scalar r, Scalar i) : re(r), im(i) { }
// ...
complex& operator+=(const complex& a)
{ re+=a.re; im+=a.im; return *this; }
// ...
private:
Scalar re, im;
};
并描述:
此类型旨在用作内置类型,声明中需要表示,以便可以创建真正的本地对象(即在堆栈上分配的对象)并且不在堆上)并确保简单操作的正确内联。
有人可以帮忙解释一下吗?在类声明中放置re
,im
数据会使类对象在堆栈上分配?内联怎么样? (我可以看到内联operator+=
,他的意思是这个吗?)
答案 0 :(得分:3)
这是一个不打算派生的具体类(因为没有必要)。
您可能不希望为复数定义接口,并导出不同类型的复数(无论是什么)并以多态方式使用它们。
通过在类中包含所有内容,编译器可以比使用抽象接口和虚函数时更容易地优化它。
我认为这里没有什么神奇之处,它只是一个使用“值类型”类合适的例子。
答案 1 :(得分:3)
成员re
和im
分配在每个complex
对象中。这意味着re
和im
在堆栈上分配,当且仅当整个complex
对象时。如果complex
对象是全局对象,那么re
和im
既不在堆栈上也不在堆上。
实际上,编译器会将re
放在对象的偏移0处,将im
放在偏移sizeof(Scalar)
处。这意味着operator+=
的代码不需要很多汇编指令来获取这些成员。实际的添加本身可能只是两个汇编指令,因此加载4个成员并存储两个结果是工作的主要部分。如果内联很少,内联工作效果最好。
答案 2 :(得分:1)
将数据放在类定义中不会生成对象 在堆栈上分配,但它允许它。在对象的位置 定义后,编译器必须知道它的完整大小;如果对象是 在堆栈上定义,编译器必须知道它的大小 定义它的翻译单位。
不将数据放入类定义意味着您必须采用 将数据分配到其他地方的一些步骤,以及其他地方的数据 几乎肯定涉及动态分配。
类似地,内联函数只能操纵它看到的数据。
有许多模式可以避免类中的数据声明。 它们具有重要的优势,特别是在数据类型时 复杂和用户定义。它们都涉及动态分配。什么 Stroustrup说,对于小型,具体的类,放置数据 在类中允许它们像内置一样表现(和执行) 类型,没有动态分配,并且经常(因为内联)没有 抽象惩罚。