最好在类中的堆栈或堆上进行分配

时间:2011-04-02 04:00:16

标签: c++ constructor

这更像是一个概念性问题,但我会提供一个特殊的实例,我对此感到疑惑。如果我有一个具有多个对象作为属性的类,最好是在类中静态分配它们,还是在构造期间动态分配它们?例如,我有以下类(发出不必要的代码)

class OutlinedText
{
protected:
    sf::Text BaseText;
    sf::Text BackgroundText;
}

sf :: Text也是对象。我的问题是,将它们如上所述更好,并按如下方式初始化它们

OutlinedText::OutlinedText(std::string &text, sf::Color MainColor, sf::Color OffsetColor, sf::Font &font, sf::Vector2f Pos, unsigned int BaseFontSize, unsigned int BackFontSize, float Offset)
{
    BaseText = sf::Text(text, font, BaseFontSize);
    BaseText.SetColor(MainColor);
    BackgroundText = sf::Text(text, font, BackFontSize);
    BackgroundText.SetColor(OffsetColor);
}

或者,我应该将它们作为指针并使用new分配它们,如下所示:

BaseText = new sf::Text(text, font, BaseFontSize);
BaseText->SetColor(MainColor);

并在析构函数中使用delete取消分配它们?我知道堆栈分配内存的速度更快,但我认为我正在以现在的方式初始化。那么它是一个案例的事情还是总是比另一个好?我还习惯于以C#的方式做事,所以我很好奇C ++的正确方法是什么。如果我有一个根本的误解,请纠正我。

提前致谢

5 个答案:

答案 0 :(得分:12)

只要有可能,您应该避免显式动态分配。动态分配相对昂贵,使对象生命周期管理更加困难。

请注意,您的问题有些误导:在第一种情况下,BaseTextBackgroundText不一定在堆栈上分配。如果它们所属的OutlinedText对象是在堆上分配的,或者是静态变量,那么它们根本不会存在于堆栈中。

  

我认为我现在正在以初始化方式进行双重处理。

您是:在输入构造函数体之前初始化每个成员变量,然后在构造函数体中,分配给成员变量,因此它们实际上是“双重初始化”。

您可以(并且在大多数情况下应该)使用the constructor's initialization list来初始化成员变量:

OutlinedText::OutlinedText(std::string& text, 
                           sf::Color MainColor, 
                           sf::Color OffsetColor,
                           sf::Font& font, 
                           sf::Vector2f Pos, 
                           unsigned int BaseFontSize, 
                           unsigned int BackFontSize, 
                           float Offset)
    : BaseText(text, font, BaseFontSize),         // This is the constructor's
      BackgroundText(text, font, BackFontSize)    // initializer list
{
    BaseText.SetColor(MainColor);
    BackgroundText.SetColor(OffsetColor);
}

这样,成员变量只被初始化一次(通过初始化列表中的初始值设定项),而不是两次。

  

我应该将它们作为指针并使用new分配它们,并在析构函数中使用delete释放它们吗?

否:您不必在C ++程序中编写delete:您应该使用智能指针(例如auto_ptrshared_ptrunique_ptr,具体取决于您需要什么样的对象生命周期)以确保自动销毁动态分配的对象。如果需要存储对象集合,则应使用标准库容器之一,如vectormapset,这些容器也会自动清理存储的对象在他们中间。

手动管理动态分配对象的生命周期是繁琐且难以正确的,应该避免。您不仅需要“在析构函数中清理”,还需要正确实现(或禁止自动生成)复制构造函数和复制赋值运算符。

C ++与C#根本不同,特别是在对象生命周期以及对象如何存在,如何复制以及何时被销毁时。如果你真的想学习这门语言,一定要确保你有a good introductory C++ book

答案 1 :(得分:2)

当詹姆斯回答你的主要问题时,我会谈谈你说的其他事情:

  

我知道堆栈分配内存的速度更快,但我认为我的初始化方式是我现在的两倍。

你在技术上不是双重初始化,但你所做的事实上效率低下。正如@Peter Huene评论的那样,您应该使用初始化列表:

OutlinedText::OutlinedText(
    std::string& text,
    sf::Color MainColor,
    sf::Color OffsetColor,
    sf::Font& font,
    sf::Vector2f Pos,
    unsigned int BaseFontSize,
    unsigned int BackFontSize,
    float Offset
)
  : BaseText(sf::Text(text, font, BaseFontSize)),
    BackgroundText(sf::Text(text, font, BackFontSize))
{
    BaseText.SetColor(MainColor);
    BackgroundText.SetColor(OffsetColor);
}

这个成语与避免明确的动态分配相结合,几乎总是首选。

此外,您似乎无法修改textfont,因此您应该按const&而不是&传递它们。 (另请考虑将MainColorOffsetColorPos传递给const&,而不是按值传递,如果它们大于sizeof(void*)。)

答案 2 :(得分:0)

如果它们适合堆栈并且您可以在ctor中将它们初始化为有用状态,那么堆栈变量可以保存您不断检查它们是否有效。

如果在稍后的某个时间点之前无法设置它们,那么使用new并将未设置的变量设置为NULL可能有意义,特别是如果变量没有明显的未设置值

答案 3 :(得分:0)

两者都没有,或者是最好的工程答案,“这取决于!”

对于小型对象,堆栈上的分配非常方便。这些对象将自动为您构建,如果您想调用备用ctor,您应该使用初始化列表来避免您提到的双重初始化问题。

较大的对象或从类中传出的对象适合于堆。

答案 4 :(得分:0)

通常,最好使用未动态分配的实例变量。

  • 当对象使用它时,您可以确定它存在

  • 它需要更少的总体记忆

  • 不太可能失败

  • 复制/移动更容易实现(通常是自动的)

  • 存储是自动的(智能指针实现不一定是线程安全的)

有时,您需要动态分配它。通常,你会知道什么时候这样做:

  • 懒惰的初始化(有时候是必要的......在某些情况下这也是一个非常糟糕的主意)

  • 多态性

  • 解耦(例如使用PIMPL减少构建依赖性)

  • 共享内存(例如,已重新计算)

  • 它的物理尺寸很大。即使物理大小很大,通常最好通过使构造函数私有来强制客户端在堆上分配容器。

你的例子:文本对象中的文本可能是动态分配的 - 文本对象本身可能会消耗几个单词。如果它只是几个字而且不需要动态分配它,那么一定要避免动态分配。

当你选择动态时,总是使用某种形式的智能指针。

祝你好运!