3

Stroustrup 的一个常见问题中,他给出了以下示例:

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;
};

并描述:

这种类型被设计为用作内置类型,并且在声明中需要表示,以便可以创建真正的本地对象(即分配在堆栈上而不是堆上的对象)并确保正确简单操作的内联

有人会解释这个吗?放reim类声明中的数据使类对象在堆栈上分配?那么内联呢?(我可以看到一个operator+=内联,他是这个意思吗?)

4

3 回答 3

3

这是一个不打算派生的具体类(因为没有必要)。

您可能不想为复数定义接口,并导出不同类型的复数(无论是什么)并以多态方式使用它们。

通过在类中包含所有内容,编译器可能比使用抽象接口和虚函数时更容易对此进行优化。

我不认为这里有什么神奇之处,这只是一个使用“值类型”类合适的例子。

于 2012-09-14T07:25:44.363 回答
3

成员reim分配在每个complex对象内部。这意味着当re仅当整个对象都im在堆栈上时才会在堆栈上分配和。如果对象是全局的,那么并且既不在堆栈上也不在堆上。complexcomplexreim

实际上,编译器会将re对象中的偏移量 0 和偏移量放置在偏移量imsizeof(Scalar)。这意味着 for 的代码operator+=不需要大量的汇编指令来获取这些成员。实际添加本身可能只是两个汇编指令,因此加载 4 个成员并存储两个结果是工作的主要部分。如果几乎不需要内联,则内联效果最好。

于 2012-09-14T07:26:15.413 回答
1

将数据放在类定义中不会使对象在堆栈上分配,但它允许它。在定义对象时,编译器必须知道它的完整大小;如果要在堆栈上定义对象,编译器必须知道它在定义它的翻译单元中的大小。

不将数据放在类定义中意味着您必须采取一些步骤将数据分配到其他地方,而其他地方几乎肯定会涉及动态分配。

同样,内联函数只能操作它看到的数据。

有多种模式可以避免类中的数据声明。它们可以具有重要的优势,尤其是当数据类型复杂且用户定义时。它们都涉及动态分配。Stroustrup 所说的是,对于小型的具体类,将数据放入类中可以让它们像内置类型一样表现(和执行),没有动态分配,并且通常(因为内联)没有抽象惩罚。

于 2012-09-14T07:57:32.663 回答