根据,用值的副本std::vector::assign替换内容。此定义意味着向量的现有状态未在中使用countvalueassign

上运行的以下示例代码中test1打印20000002。这表明在分配新空间后删除了现有内存。通过合理放置clear()&shrink_to_fit()调用,test2可以减少峰值对象数并打印10000002

这是实现过程中错失的优化机会libstdc++还是我对std::vector功能的理解不正确/不完整?

#include <iostream>
#include <vector>

int curr;
int max;

void update(int delta) { curr += delta; max = std::max(max, curr); }
void reset() { curr = max = 0; }

struct Foo {
    Foo() { update(1); }
    Foo(const Foo&) { update(1); }
    Foo& operator=(const Foo&) { return *this; }
    ~Foo() { update(-1); }
};

void test1() 
{
    reset();
    std::vector<Foo> foos;
    foos.assign(10'000'000, Foo{});
    foos.assign(10'000'001, Foo{});
    std::cout << max << '\n';
}

void test2() 
{
    reset();
    std::vector<Foo> foos;
    foos.assign(10'000'000, Foo{});
    foos.clear();
    foos.shrink_to_fit();
    foos.assign(10'000'001, Foo{});
    std::cout << max << '\n';
}

int main()
{
    test1();
    test2();
    return 0;
}

12

  • 6
    除了析构函数之外,任何一个向量 API 都不会释放内存shrink_to_fit()


    – 


  • 2
    那么,如果其中一个赋值运算符引发异常,向量的状态会怎样?


    – 

  • 3
    @3CxEZiVlQ 我认为甚至不能保证内存释放(“将 capacity() 减少到 size() 是一个非约束性请求。请求是否得到满足取决于实现。”)。我相信只有析构函数才能保证这一点。


    – 


  • 2
    想象一下会引发 bad_alloc 吗?或者在创建新值时引发其他异常。


    – 


  • 1
    @MarshallClow,这个问题问得非常好。恕我直言,添加noexcept示例代码不会改变答案。当然,new可能会引发异常,这意味着至少在新分配完成之前,删除现有内存是不安全的。


    – 


最佳答案
1

观察到的 20mil 意味着有那么一刻,第二个assign对象创建了,但第一个对象assign尚未被删除。

事实上,第二个assign更大。因此,如果 vector 为第一个分配了精确数量的内存,那么它应该为第二个创建另一块内存。它将在其中创建第二个对象加载,将值增加到20 mil。assignassignmax

然后它释放为前 1000 万个元素分配的内存。

它不会提前释放第一个内存块,因为在构造第二个内存块时可能会发生异常。所以只有在保证第二个内存块分配和填充成功的情况下,才会释放第一个内存块。

您的问题是正确的:assign没有提供强有力的异常保证,即,如果第二个assign不需要重新分配,那么很可能不会重新分配。

但如果重新分配不可避免,他们会选择提供强有力的担保。

以下是对数字发生的情况的解释:

void test1() 
{
    reset();
    std::vector<Foo> foos;
    
    foos.assign(10'000'000, Foo{});
    // allocated underlying memory for first bunch of elements
    // constructed first 10mil elements

    foos.assign(10'000'001, Foo{});
    // allocated underlying memory for second bunch of elements
    // constructed second 10mil elements,
    // max reached 20mil
    //then released first 10mil elements and their underlying memory 
    std::cout << max << '\n';
}

void test2() 
{
    reset();
    std::vector<Foo> foos;
    foos.assign(10'000'000, Foo{}); // constructed first 10 mil elements, `curr` == 10mil
    foos.clear(); // deleted elements, reducing `curr` to 0.
    foos.shrink_to_fit(); // released underlying memory
    foos.assign(10'000'001, Foo{});
    // allocated underlying memory for 10mil+1 elements
    // constructed those elements, `curr` modified from 0 to 10mil+1
    std::cout << max << '\n'; // prints 10mil+1
}

3

  • 第一组将在第二组成功构建后销毁。因为如果在第二组构建过程中发生异常,第一组应该保持不变。


    – 

  • 哦没关系,我误解了楼主的代码。这一切都说得通。


    – 


  • 也许你可以测试一下如果不可移动会发生什么Foo{},因为这样就没有必要为了异常安全而创建两个单独的巨大分配。


    –