简体   繁体   English

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

[英]C++ : Vector Allocator behavior, memory allocation and smart pointers

Refer to the following code snippet. 请参考以下代码段。

According to my understanding: 据我了解:

a) 'p1' and 'p2' objects are created in the stack and get destroyed at the end of the getPoints() method. a) 'p1'和'p2'对象在堆栈中创建,并在getPoints()方法的末尾销毁。

b) When p1 and p2 are added to the vector using push_back(), the default Allocator creates new instances of Point and copy the values(x,y) of p1 and p2 into these newly created instances. b)当使用push_back( p1p2添加到向量中时,默认分配器将创建Point的新实例,并将p1p2的值(x,y)复制到这些新创建的实例中。

My questions are: 我的问题是:

1) Is my understanding correct? 1)我的理解正确吗?

If so ; 如果是这样的话 ;

2) If new Point objects are created by the Allocator, why I see only two lines of "Points created"? 2)如果新的Point对象是由分配器创建的,为什么我只能看到两行“ Points created”?

Because I expect to see two lines for p1 and p2 and also two lines for newly created object by the Allocator. 因为我希望看到p1p2的两行,以及分配器新创建的对象的两行。

3) How does the Allocator assign original values to x,y fields of the newly created objects? 3)分配器如何将原始值分配给新创建对象的x,y字段? Does it using raw memory copy? 是否使用原始内存副本?

4) Is the shared pointer the recommended way to return a vector from a method? 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";
    }

}

Q: Is my understanding correct? 问:我的理解正确吗?

A: Your understanding is partially correct. 答:您的理解部分正确。

  • p1 and p2 are created on the stack, using the default no-argument constructor, that you've defined. p1和p2是使用已定义的默认无参数构造函数在堆栈上创建的。
  • The Default allocator may be used to allocate more memory for p1 and p2 when you call push_back(), but will not always do so. 默认分配器用于P1和P2,当你调用的push_back(分配更多的内存),但不会一直做下去。 It will never create default construct a new instance of Point though. 它永远不会创建默认构造Point的新实例。

Q: If new Point objects are created by the Allocator, why I see only two lines of "Points created"? 问:如果分配器创建了新的Point对象,为什么我只能看到两行“ Points created”?

A: New objects are not being created by the allocator - the allocator only allocates more memory, if needed . 答:分配器不会创建新对象-分配器仅在需要时分配更多内存。 The objects that you insert in the vector are copy constructed. 您插入到向量中的对象是复制构造的。 Because you have not created a copy constructor, the compiler has generated one for you. 由于尚未创建副本构造函数,因此编译器已为您生成了一个副本构造函数。

Q: How does the Allocator assign original values to x,y fields of the newly created objects? 问:分配器如何将原始值分配给新创建对象的x,y字段? Does it using raw memory copy? 是否使用原始内存副本?

A: As stated in the previous question, the allocator only allocates memory and does not create or destroy objects. 答:如上一个问题所述,分配器仅分配内存,而不创建或销毁对象。 The behavior of copying the fields is done by the copy constructor that gets invoked when you do a push_back . 复制字段的行为是由执行push_back时调用的复制构造函数完成的。 The automatically generated copy constructor will do a member-wise copy construction of each of the class' members. 自动生成的副本构造函数将对每个类的成员进行成员级的副本构造。 In your case, x and y are primitive types, so they'll be just raw memory copied. 在您的情况下, xy是原始类型,因此它们将只是原始内存复制。 If members were complex objects, their copy constructors would be invoked. 如果成员是复杂对象,则将调用其副本构造函数。

Q: Is the shared pointer the recommended way to return a vector from a method? 问:共享指针是从方法返回向量的推荐方法吗?

A: This would depend on your use case, and is opinion based. 答:这将取决于您的用例,并且基于意见。 My personal advice, and this is for all kind of objects is: 我的个人建议(针对所有对象)是:

  • If your use cases allows it, return by value (that is, std::vector<Point> getPoints() ) 如果您的用例允许, std::vector<Point> getPoints()值返回(即std::vector<Point> getPoints()
  • If you need dynamically allocated storage, or the object that you want to return can be nothing, because construction failed, return by std::unique_ptr . 如果您需要动态分配的存储,或者要返回的对象什么都不是,因为构造失败,请通过std::unique_ptr返回。 This applies to pretty much all factory functions that you might want to create. 这几乎适用于您可能要创建的所有工厂功能。 Even if you later want to share the ownership (see point 3), you can construct a shared_ptr by moving from a unique_ptr ( std::shared_ptr<T> shared = std::move(unique) ); 即使以后要共享所有权(请参阅第3点),也可以通过从unique_ptr( std::shared_ptr<T> shared = std::move(unique)std::shared_ptr<T> shared = std::move(unique)来构造shared_ptr;
  • Avoid using shared_ptr unless you really need shared ownership of the ptr. 除非确实需要ptr的共享所有权 ,否则避免使用shared_ptr shared_ptr are more complex to reason about, can create hard to debug cycles, leading to memory leaks, and are heavier in terms of performance (because of atomic operations relating to their refcount and additional allocated memory for a control block). shared_ptr的推论更为复杂,可能造成难以调试的周期,导致内存泄漏,并且在性能方面更为沉重(因为与引用计数和为控制块分配的额外内存相关的原子操作)。 If you think you need a shared_ptr , reconsider your design and think if you can use a unique_ptr instead. 如果您认为需要shared_ptr ,请重新考虑设计并考虑是否可以使用unique_ptr

How this works: 工作原理:

Internally, a std::vector is using memory allocated using the default allocator (or custom user provided, if you provided one) on the heap. 在内部,std :: vector正在使用通过堆上的默认分配器(或提供的自定义用户,如果提供了自定义用户)分配的内存。 This allocation happens behind the scenes, and is independent of the vector's size, and from the number of elements in the vector (but is always >= size() ). 这种分配发生在幕后,并且与向量的大小以及向量中元素的数量无关(但始终> = size() )。 You can get how many elements the vector has allocated storage for by using the capacity() function. 您可以通过使用capacity()函数获得向量为存储分配了多少个元素。 When you call push_back() , what happens: 当您调用push_back() ,会发生什么:

  1. If there is enough storage (as determined by capacity() ) to hold one more element, the argument that you passed to push_back is copy constructed , using the copy constructor if using the push_back( const T& value ) variant or moved from if using push_back( T&& value ) , by using the move constructor . 如果有足够的存储空间(由capacity()确定)可以容纳一个以上的元素,则传递给push_back的参数是copy Constructed ,如果使用push_back( const T& value )变体,则使用copy构造push_back( const T& value ) ;如果使用push_back( T&& value )则从from移出push_back( T&& value ) ,使用move构造函数。
  2. If there is no more memory (ie the new size() > capacity), more memory is allocated that will be sufficient to hold the new elements. 如果没有更多的内存(即新的size()>容量),则会分配更多的内存,足以容纳新的元素。 How much memory will be allocated is implementation defined. 实现将分配多少内存。 A common pattern is to double the amount of capacity that the vector previously had until a threshold, after which memory is allocated in buckets. 一种常见的模式是将向量以前具有的容量增加一倍,直到达到阈值为止,然后在存储桶中分配内存。 You may use reserve() before inserting elements, to make sure that your vector will have enough capacity to hold at least as many elements without new allocations. 您可以在插入元素之前使用reserve() ,以确保您的向量将具有足够的容量来容纳至少相同数量的元素而无需新分配。 After new memory has been allocated, the vector reallocates all existing elements into the new storage by either copying them, or moving them if they are not copy-insertable. 分配新内存后,向量将通过复制所有现有元素或将其移动到无法复制插入的方式将它们重新分配到新存储中。 This reallocation will invalidate all iterators and references to elements in the vector (caveat: the rules for when exactly copy vs move will be used when reallocating is a bit more complex, but this is the general case) 这种重新分配将使向量中的所有迭代器和对元素的引用无效(注意:重新分配时将使用精确复制与移动的规则,但这有点复杂,但这是一般情况)

Add a copy constructor to the Point class to see what is happening. 将复制构造函数添加到Point类以查看发生了什么。

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

You will see the statement printed five times if you are using GCC compiler. 如果您使用的是GCC编译器,则会看到该语句打印了五次。 In the getPoints function, once for the first push_back , twice for the next push_back because the vector is resized and all elements are inserted again. getPoints函数中,一次用于第一个push_back ,两次用于下一次push_back因为调整了向量的大小并再次插入了所有元素。 The fourth and the fifth time will be for the for loop within main . 第四和第五次将是main内的for循环。

You can eliminate three copies using reserve to set the capacity of the vector in the getPoints function, 您可以使用reserve来消除三个副本 ,以在getPoints函数中设置vector的容量,

ret->reserve(2);

and by using references in the for loop of the main . 并通过在mainfor循环中使用引用。

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

1) Is my understanding correct? 1)我的理解正确吗?

[Ans]Yes partially correct. [回答]是部分正确。 Objects p1 and p2 are created in stack, but when pushed to vector it invoked copy constructor for creating and initializing new objects. 对象p1和p2是在堆栈中创建的,但是当推入向量时,它会调用复制构造函数来创建和初始化新对象。

2) If new Point objects are created by the Allocator, why I see only two lines of "Points created"? 2)如果新的Point对象是由分配器创建的,为什么我只能看到两行“ Points created”? Because I expect to see two lines for p1 and p2 and also two lines for newly created object by the Allocator. 因为我希望看到p1和p2的两行,以及分配器新创建的对象的两行。

[Ans]Using copy constructor. [回答]使用复制构造函数。 Please add a copy constructor and you will see the difference. 请添加一个副本构造函数,您将看到区别。

3) How does the Allocator assign original values to x,y fields of the newly created objects? 3)分配器如何将原始值分配给新创建对象的x,y字段? Does it using raw memory copy? 是否使用原始内存副本? [Ans]Using copy constructor and vector itself is a dynamic array which reallocates memory as it needed. [答案]使用复制构造函数和向量本身是一个动态数组,可以根据需要重新分配内存。

4) Is the shared pointer the recommended way to return a vector from a method? 4)共享指针是从方法返回向量的推荐方法吗? [Ans] Depends on your use case as well. [回答]还要取决于您的用例。 You can pass reference to a vector as a parameter and return the same. 您可以将对矢量的引用作为参数传递并返回它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM