C++:向量分配器行为、内存分配和智能指针

Ash*_*iya 2 c++ stl shared-ptr stdvector allocator

请参阅以下代码片段。

根据我的理解:

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";
    }

}
Run Code Online (Sandbox Code Playgroud)

div*_*nas 5

问:我的理解正确吗?

答:你的理解是部分正确的。

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

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

答:分配器不会创建新对象 - 分配器仅在需要时分配更多内存。您在向量中插入的对象是复制构造的。因为您还没有创建复制构造函数,所以编译器为您生成了一个。

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

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

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

答:这取决于您的用例,并且是基于意见的。我个人的建议,这适用于所有类型的对象是:

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

这是如何工作的:

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

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