在堆栈(带有副本)或堆(带有指针)上分配对象

allocate objects on the stack (with copies) or heap (with pointers)

本文关键字:对象 分配 指针 副本 堆栈 或堆      更新时间:2023-10-16

我有一些关于使用堆栈和堆的基本问题。有很多答案,但我不确定如何将其与以下内容联系起来。

我认为用例子来解释比较容易。

这样做"更好"吗

struct Widget3 {
    Widget3(std::shared_ptr<Widget1> widget1, std::shared_ptr<Widget2> widget2)
    : _widget1(std::move(widget1))
    , _widget2(std::move(widget2))
    {
    }
    void doSomething()
    {
        std::cout << _widget1.hello() << _widget2.hello();
    }
private:
    std::shared_ptr<Widget1> _widget1;
    std::shared_ptr<Widget2> _widget2;
};

struct Widget3 {
    Widget3(const Widget1 & widget1, const Widget2 & widget2)
    : _widget1(widget1)
    , _widget2(widget2)
    {
    }
    void doSomething()
    {
        std::cout << _widget1->hello() << _widget2->hello();
    }
private:
    Widget1 _widget1;
    Widget2 _widget2;
};

第二个示例导致我们复制Widget1Widget2,但假设我们在程序开始时一次性创建所有这些对象,并且不关心复制对象的成本(性能和内存)。

是否以某种方式更快地使用第二个例子,其中_widget1_widget2在堆栈上?

第二种方法更好,因为如果您选择不使用智能指针,则没有内存泄漏的机会。

不能保证所有操作都在堆栈上完成。

如果我写:

Widget1 widget1;
Widget2 widget2;
Widget3 *widget3 = new Widget3(widget1, widget2);
int main(){
    // do something with 'widget3'
    delete widget3;
}

那么呢?

你应该尽可能不使用指针,这样更安全。但是,如果你的数据有多个句柄,并且你不能通过引用传递(无论出于什么疯狂的原因),那么无论如何,请使用示例1。

关键是在这种情况下哪种方法更好。 不可能总是优于其他方法。

首先,这两个Widget3实现是不一样的。使用shared_ptr的Widget3的第一个实现将简单地移动指针(不会复制Widget1或Widget2)。但是,第二个实现将把Widget1和Widget2复制到成员变量中。

证明如下:

#include <iostream>
#include <memory>
namespace
{
    std::ostream& os = std::cout;
}
struct Widget1
{
    Widget1()
    {
        os << __FUNCSIG__ << std::endl;
    }
    Widget1( const Widget1& rhs )
    {
        os << __FUNCSIG__ << std::endl;
    }
    Widget1( Widget1&& rhs )
    {
        os << __FUNCSIG__ << std::endl; 
    }
};
struct Widget2
{
    Widget2()
    {
        os << __FUNCSIG__ << std::endl;
    }
    Widget2( const Widget2& rhs )
    {
        os << __FUNCSIG__ << std::endl;
    }
    Widget2( Widget2&& rhs )
    {
        os << __FUNCSIG__ << std::endl;
    }
};
struct Widget3_1 {
    Widget3_1(std::shared_ptr<Widget1> widget1, std::shared_ptr<Widget2> widget2)
    : _widget1(std::move(widget1))
    , _widget2(std::move(widget2))
    {
    }
private:
    std::shared_ptr<Widget1> _widget1;
    std::shared_ptr<Widget2> _widget2;
};

struct Widget3_2 {
    Widget3_2(const Widget1 & widget1, const Widget2 & widget2)
    : _widget1(widget1)
    , _widget2(widget2)
    {
    }
private:
    Widget1 _widget1;
    Widget2 _widget2;
};

int main()
{
    const int repeatCount = 1;
    {
        os << " Widget3_1 : " << std::endl;
        std::shared_ptr<Widget1> widget1( new Widget1 );
        std::shared_ptr<Widget2> widget2( new Widget2 );
        Widget3_1(widget1, widget2);
    }
    {
        os << " Widget3_2 : " << std::endl;
        Widget1 widget1;
        Widget2 widget2;
        Widget3_2(widget1, widget2);
    }
} // int main
输出:

 Widget3_1 :
__thiscall Widget1::Widget1(void)
__thiscall Widget2::Widget2(void)
 Widget3_2 :
__thiscall Widget1::Widget1(void)
__thiscall Widget2::Widget2(void)
__thiscall Widget1::Widget1(const struct Widget1 &)
__thiscall Widget2::Widget2(const struct Widget2 &)
你不仅有其他事情要担心,而且我认为他们之间不会有任何区别。随着项目规模的扩大,差异可能会越来越大,但除了测试之外,没有其他方法可以证明到底发生了什么。为了说服你,让我们给你一些测试结果。这是在VisualC, windows 8.1上测试的
#include <iostream>
#include <string>
#include <memory>
#include <intrin.h>
namespace
{
    std::ostream& os = std::cout;
    typedef decltype(__rdtsc()) ClockType;
    ClockType clock;
    void MeasureBegin()
    {
        clock = __rdtsc();
    }
    ClockType MeasureEnd()
    {
        return __rdtsc() - clock;
    }
}
struct Widget1
{
    Widget1()
        : a(0),b(1),c(2),d(3)
    {
    }
    int a,b,c,d;
};
struct Widget2
{
    Widget2()
        :j ("Hello, World")
    {
    }
    std::string j;
};
struct Widget3 {
    Widget3(const Widget1 & widget1, const Widget2 & widget2)
    : _widget1(widget1)
    , _widget2(widget2)
    {
    }
private:
    Widget1 _widget1;
    Widget2 _widget2;
};
int main()
{
    const int testCount = 10;
    const int repeatCount = 100000;
    ClockType c = 0;
    for( int j = 0; j < testCount; ++j )
    {
        Widget1 widget1;
        Widget2 widget2;
        MeasureBegin();
        for (int i = 0; i < repeatCount; ++i)
        {
            Widget3(widget1, widget2);
        }
        c = MeasureEnd();
        std::cout << " 1 : " << c << std::endl;
        Widget1* pwidget1 = new Widget1();
        Widget2* pwidget2 = new Widget2();
        MeasureBegin();
        for (int i = 0; i < repeatCount; ++i)
        {
            Widget3(*pwidget1, *pwidget2);
        }
        c = MeasureEnd();
        std::cout << " 2 : " <<  c << std::endl;
        delete pwidget1;
        delete pwidget2;
    }
} // int main
输出:

1 : 5030133
2 : 5726116
1 : 4898262
2 : 5915359
1 : 4805982
2 : 4988638
1 : 4758595
2 : 5065566
1 : 5470122
2 : 5312685
1 : 5133652
2 : 5753500
1 : 5475479
2 : 5196111
1 : 5153481
2 : 5678220
1 : 4790526
2 : 4820254
1 : 4836252
2 : 5231158

没有区别。