【问题标题】:C++ : Vector Allocator behavior, memory allocation and smart pointersC++:向量分配器行为、内存分配和智能指针
【发布时间】:2019-03-09 13:39:11
【问题描述】:

参考以下代码sn-p。

据我了解:

a) 'p1' 和 'p2' 对象在堆栈中创建,并在 getPoints() 方法结束时被销毁。

b) 当使用 push_back() 将 p1p2 添加到向量中时,默认分配器会创建 Point 的新实例 并将 p1p2 的值 (x,y) 复制到这些新创建的实例中。

我的问题是:

1) 我的理解正确吗?

如果是这样;

2) 如果分配器创建了新的 Point 对象,为什么我只看到两行“Points created”?

因为我希望看到 p1p2 的两行,以及分配器新创建的对象的两行。

3) 分配器如何将原始值分配给新创建对象的 x,y 字段?它使用原始内存副本吗?

4) 共享指针是从方法返回向量的推荐方式吗?

#include <iostream>
#include <vector>

using namespace std;

struct Point {
    Point() {
        std::cout<< "Point created\n";
        x=0;
        y=0;
    }
    int x;
    int y;
};


std::shared_ptr< vector<Point> > getPoints() {
    std::shared_ptr< vector<Point> > ret =  std::make_shared< vector<Point> >();
    Point p1;
    p1.x=100;
    p1.y=200;

    Point p2;
    p2.x = 1000;
    p2.y = 2000;

    ret->push_back(p1);
    ret->push_back(p2);

    return ret;
}

int main(int argc, char** argv)
{
    std::shared_ptr< vector<Point> > points = getPoints();
    for(auto point : *(points.get())) {
        std::cout << "Point x "<<point.x << " "<< point.y<<"\n";
    }

}

【问题讨论】:

  • 调用了默认的复制构造函数,这就是为什么你只看到两行。
  • @101010 "Point()" 不是默认构造函数吗?
  • Point() 是您定义的默认构造函数。但是当推回一个向量时,复制构造函数被调用。您尚未定义复制构造函数,因此编译器会在幕后为您定义一个。
  • 如果你需要一个对象的共享所有权,你应该使用shared_ptr。一个好的默认值是只返回一个vector
  • 请注意,最好使用member initializer list 编写构造函数,而不是在主体中使用assingments,例如Point::Point() : x(0), y(0) { std::cout &lt;&lt; "Point created\n"; }

标签: c++ stl shared-ptr stdvector allocator


【解决方案1】:

问:我的理解正确吗?

答:您的理解部分正确。

  • p1 和 p2 是使用您定义的默认无参数构造函数在堆栈上创建的。
  • 默认分配器可能在您调用 push_back() 时用于为 p1 和 p2 分配更多内存,但并不总是这样做。但它永远不会创建默认构造 Point 的新实例。

问:如果分配器创建了新的 Point 对象,为什么我只看到两行“Points created”?

答:分配器不会创建新对象 - 分配器只会分配更多内存,如果需要。您插入向量中的对象是复制构造的。因为你还没有创建拷贝构造函数,编译器已经为你生成了一个。

问:分配器如何将原始值分配给新创建对象的 x,y 字段?它是否使用原始内存副本?

答:如上一题所述,分配器只分配内存,不会创建或销毁对象。复制字段的行为是由当您执行push_back 时调用的复制构造函数完成的。自动生成的复制构造函数将对每个类的成员进行成员方式的复制构造。在您的情况下,xy 是原始类型,因此它们只是复制的原始内存。如果成员是复杂对象,则将调用其复制构造函数。

问:共享指针是从方法返回向量的推荐方式吗?

答:这取决于您的用例,并且是基于意见的。我个人的建议是:

  • 如果您的用例允许,则按值返回(即std::vector&lt;Point&gt; getPoints()
  • 如果你需要动态分配存储,或者你想要返回的对象可以是空的,因为构造失败,返回std::unique_ptr。这适用于您可能想要创建的几乎所有工厂函数。即使您以后想要共享所有权(参见第 3 点),您也可以通过从 unique_ptr (std::shared_ptr&lt;T&gt; shared = std::move(unique)) 移动来构造 shared_ptr;
  • 避免使用shared_ptr,除非您确实需要共享所有权 ptr。 shared_ptr 的推理更复杂,可能会创建难以调试的周期,导致内存泄漏,并且在性能方面更重(因为与引用计数相关的原子操作和为控制块分配的额外内存)。如果您认为需要 shared_ptr,请重新考虑您的设计并考虑是否可以改用 unique_ptr

这是如何工作的:

在内部,std::vector 正在使用堆上的默认分配器(或自定义用户提供的,如果您提供的话)分​​配的内存。这种分配发生在幕后,并且与向量的大小和向量中的元素数量无关(但始终 >= size())。您可以使用capacity() 函数获取向量为多少元素分配了存储空间。当您拨打push_back() 时,会发生什么:

  1. 如果有足够的存储空间(由capacity() 确定)来容纳更多元素,则传递给 push_back 的参数是复制构造,如果使用push_back( const T&amp; value ),则使用复制构造函数如果使用 push_back( T&amp;&amp; value ),则使用移动构造函数从变体或移动。
  2. 如果没有更多内存(即新的 size() > 容量),则会分配更多内存,足以容纳新元素。将分配多少内存是实现定义的。一种常见的模式是将向量先前拥有的容量翻倍,直到达到阈值,然后在存储桶中分配内存。您可以在插入元素之前使用reserve(),以确保您的向量将有足够的容量来容纳至少尽可能多的元素而无需新的分配。分配新内存后,向量将所有现有元素重新分配到新存储中,方法是复制它们,或者如果它们不可复制插入,则移动它们。这种重新分配将使向量中的所有迭代器和对元素的引用无效(警告:当重新分配有点复杂时,将使用何时精确复制与移动的规则,但这是一般情况)

【讨论】:

    【解决方案2】:

    Point 类添加一个复制构造函数,看看发生了什么。

    Point(const Point& p) {
        std::cout<< "Point copied\n";
        this->x = p.x;
        this->y = p.y;
    }
    

    如果您使用 GCC 编译器,您将看到该语句打印了五次。在getPoints 函数中,第一个push_back 一次,下一个push_back 两次,因为调整了向量的大小并再次插入了所有元素。 第四次和第五次将针对main 内的for 循环。

    您可以在getPoints函数中使用reserve设置vector的容量来消除三个副本

    ret->reserve(2);
    

    并在mainfor 循环中使用引用。

    for(auto& point : *(points.get())) {
            std::cout << "Point x "<<point.x << " "<< point.y<<"\n";
    }
    

    【讨论】:

    • 可能看到打印了五次的声明。确切的扩展策略由实现定义。
    【解决方案3】:

    1) 我的理解正确吗?

    [Ans]是的,部分正确。对象 p1 和 p2 在堆栈中创建,但当推送到向量时,它会调用复制构造函数来创建和初始化新对象。

    2) 如果分配器创建了新的 Point 对象,为什么我只看到两行“Points created”? 因为我希望看到 p1 和 p2 的两行,以及分配器新创建的对象的两行。

    [Ans]使用复制构造函数。请添加一个复制构造函数,您将看到不同。

    3) 分配器如何将原始值分配给新创建对象的 x,y 字段?它是否使用原始内存副本? [Ans]使用拷贝构造函数和向量本身是一个动态数组,它会根据需要重新分配内存。

    4) 共享指针是从方法返回向量的推荐方式吗? [Ans] 也取决于您的用例。您可以将向量的引用作为参数传递并返回。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-03-23
      • 1970-01-01
      • 2020-03-11
      • 1970-01-01
      • 2021-11-29
      • 2021-12-26
      • 2016-02-21
      相关资源
      最近更新 更多