在类声明中放置数据?

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+=,他是指这个吗?)


我已经删除了第二个问题。如果您想再次提问,请提供您想知道的源代码。 - MSalters
你为什么把“complex”称为“基类”?这样的命名会让问题标题产生误导。 - juanchopanza
谢谢 juanchopanza。已经更正了。 - user1559625
3个回答

3

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

你可能不想为复数定义接口并派生不同种类的复数(无论那是什么),并在多态情况下使用它们。

通过将所有内容放入类中,编译器可以更容易地进行优化,而不是使用抽象接口和虚函数。

我认为这里没有任何魔法,这只是一个适合使用“值类型”类的示例。


谢谢Bo。当我阅读Stroustrup的一些常见问题解答时,有时候他的解释方式会让我感到困惑。我经常尝试确保我没有错过重要的要点。 - user1559625

3
complex对象中的reim成员分配在每个对象内部。这意味着,reim仅在整个complex对象位于堆栈上时才被分配在堆栈上。如果complex对象是全局的,则reim既不在堆栈上也不在堆上。
实际上,编译器会将re放置在对象的偏移量为0的位置,而im则放置在sizeof(Scalar)的偏移量处。这意味着operator+=的代码不需要很多汇编指令来获取这些成员。实际的加法本身可能只是两个汇编指令,因此加载4个成员并存储两个结果是工作的主要部分。如果要内联的内容很少,则内联运行得最好。

1

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

如果不将数据放在类定义中,则必须采取一些步骤将数据分配到其他地方,而那里几乎肯定涉及动态分配。

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

有各种模式可以避免在类中声明数据。它们可以具有重要优势,特别是当数据类型复杂且用户定义时。它们都涉及动态分配。Stroustrup 的意思是对于小型、具体的类,将数据放在类中使它们像内置类型一样行为(和执行),没有动态分配,并且通常(由于内联)没有抽象惩罚。


谢谢James。写得很好,很有意义。 - user1559625

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接