在方法中实例化对象与创建类成员

时间:2015-09-12 02:15:14

标签: c++

实例化方法中所需的对象有什么理由,而不是使对象成为类成员?

例如,在下面的示例代码中,我有一个类ClassA,我希望从另一个类中使用,例如USer1,它具有指向classA的对象的指针作为成员变量并在其构造函数中实例化,另一方面User2,它在使用它之前在方法中实例化classA的对象。有什么理由以单向与另一种方式做到这一点?

class ClassA
{
    public:
    void doStuff(void){ }
};

//
// this class has ClassA as a member
//
class User1
{
    public:
        User1()
        {
            classA = new ClassA();
        }

        ~User1()
        {
            delete classA;
        }

        void use(void)
        {
            classA->doStuff();
        }        
    private:
        ClassA *classA;
};

//
// this class uses ClassA only in a method
//
class User2
{
    public: 
        void use(void)
        {
            ClassA *classA = new ClassA();
            classA->doStuff();
            delete classA;
        }    
};

int main(void)
{
    User1 user1;
    user1.use();

    User2 user2;
    user2.use();

    return 0;
}

2 个答案:

答案 0 :(得分:2)

使其成为集体成员的好处是:

  • 您不必每次都分配实例,这取决于类可能非常慢。
  • 会员可以存储状态(虽然有些人会说这是一个坏主意)
  • 少代码

作为旁注,如果你只是在构造函数和析构函数中使用new和delete进行实例化和删除,它应该不是一个指针,只是一个成员实例,然后摆脱新的和删除。

IE

class User1
{
public:
    void use(void)
    {
        classA.doStuff();
    }        
private:
    ClassA classA;
};

有时情况并非如此,例如,当堆栈上分配的类很大,或者您希望保持类的占用空间尽可能小时。但这些是例外而不是规则。

还有其他需要考虑的事情,如内存碎片,访问连续内存块的优势,以及如何在目标系统上分配内存。没有银子弹,只有一般建议,对于任何特定的程序,您需要测量和调整以获得最佳性能或克服特定程序的限制。

内存碎片即使你有大量内存可用,但是单个块​​的大小非常小,当你尝试分配大量内存时会出现内存错误。这通常是由创建和销毁各种大小的不同对象引起的,其中一些对象保持活力。如果您的系统存在内存碎片问题,我建议您彻底分析如何创建对象,而不是担心成员是否会影响系统。但是,以下是当您遇到内存碎片时四种不同场景如何发挥作用的细分:

  • 在堆栈上实例化类非常有用,因为它不会导致整体内存碎片。
  • 将其创建为值成员可能会导致问题,因为它可能会增加对象的整体大小,因此当您到达碎片方案时,该对象可能太大而无法创建。
  • 创建对象并存储指向它的指针可能会增加内存碎片
  • 在堆上分配并在使用结束时删除可能会增加内存碎片,如果在其之后分配了其他内容。

访问连续内存的优点是缓存未命中最小化,所以我的感觉是将对象作为值成员会更快,但是由于很多事情依赖于许多其他变量,这可能是完全错误的。与性能一样,衡量。

内存通常与特定边界对齐,例如4字节对齐,或2个块的功率。因此,当您分配其中一个时,取决于对象的大小,它可能会占用比您预期更多的内存,如果您分配的对象包含任何成员,如果它是值成员,则可能会显着改变类的内存占用,或者如果它根本不会增加它,而拥有指向它的指针肯定会增加指针大小的足迹,这可能会导致显着增加。在堆或堆栈上创建类不会影响使用类的大小。与往常一样,如果它会影响您的程序,您需要在目标系统上进行测量,以查看效果将会是什么。

如果构造函数/析构函数执行某些操作(例如文件句柄,打开文件和关闭文件),那么您可能只想在函数中使用它。但又一次,指针通常不是必需的。

void use(void)
{
    ClassA classA;
    classA.doStuff();
} //classA will be destructed at end of scope 

答案 1 :(得分:2)

首先,没有理由在任何一个类中都有指针。如果我们在User1中使用value semantics,则不需要构造函数或析构函数,因为编译器生成的函数就足够了。这会将User1更改为:

class User1
{
public:
    void use(void)
    {
        classA.doStuff();
    }        
private:
    ClassA classA;
};

同样,如果我们在User2中使用值语义,那么它将成为:

class User2
{
public: 
    void use(void)
    {
        ClassA classA;
        classA.doStuff();
    } 
};

现在,您是否希望将ClassA作为成员,或者您是否应该在功能中使用它是一个设计问题。如果该类将使用并更新ClassA,那么它应该是成员。如果你只是需要在函数中做某事,第二种方法是可以的。

如果你打算多次调用创建ClassA的函数,那么将它作为成员可能是有益的,因为你只需要构造它一次就可以在函数中使用它。相反如果你想拥有很多物品,但你几乎没有调用过这个功能,那么在需要时创建ClassA可能会更好,因为这会节省空间。

实际上,您需要对此进行分析,以确定哪种方式会更好。我们程序员对什么是更快的判断是错误的,应该让剖析器告诉我们是否需要改变一些东西。对于使用堆分配的指针使用值语义的一些事情通常更快。我们弄错了的一个例子就是排序。如果N很小,那么使用O(n^2)的冒泡排序比O(n log n)的快速排序要快。这个si的另一个例子是在Hurb Sutter talk从46:00开始提出的。他表示,在插入和删除中间时,使用std::vectorstd::list更快,因为std::vector非常缓存,而std::list则不是。{/ p >