簡體   English   中英

實現std :: vector :: push_back強大的異常安全性

[英]Implementing std::vector::push_back strong exception safety

我正在根據2018年后的聖地亞哥草案( N4791 )實施我自己的載體,並對實施強大的異常安全性有一些疑問。

這是一些代碼:

template <typename T, typename Allocator>
void Vector<T, Allocator>::push_back(const T& value)
{
    if (buffer_capacity == 0)
    {
        this->Allocate(this->GetSufficientCapacity(1));
    }
    if (buffer_size < buffer_capacity)
    {
        this->Construct(value);
        return;
    }
    auto new_buffer = CreateNewBuffer(this->GetSufficientCapacity(
        buffer_size + 1), allocator);
    this->MoveAll(new_buffer);
    try
    {
        new_buffer.Construct(value);
    }
    catch (...)
    {
        this->Rollback(new_buffer, std::end(new_buffer));
        throw;
    }
    this->Commit(std::move(new_buffer));
}

template <typename T, typename Allocator>
void Vector<T, Allocator>::Allocate(size_type new_capacity)
{
    elements = std::allocator_traits<Allocator>::allocate(allocator,
        new_capacity);
    buffer_capacity = new_capacity;
}

template <typename T, typename Allocator> template <typename... Args>
void Vector<T, Allocator>::Construct(Args&&... args)
{
    // TODO: std::to_address
    std::allocator_traits<Allocator>::construct(allocator,
        elements + buffer_size, std::forward<Args>(args)...);
    ++buffer_size;
}

template <typename T, typename Allocator>
Vector<T, Allocator> Vector<T, Allocator>::CreateNewBuffer(
    size_type new_capacity, const Allocator& new_allocator)
{
    Vector new_buffer{new_allocator};
    new_buffer.Allocate(new_capacity);
    return new_buffer;
}

template <typename T, typename Allocator>
void Vector<T, Allocator>::Move(iterator first, iterator last, Vector& buffer)
{
    if (std::is_nothrow_move_constructible_v<T> ||
        !std::is_copy_constructible_v<T>)
    {
        std::move(first, last, std::back_inserter(buffer));
    }
    else
    {
        std::copy(first, last, std::back_inserter(buffer));
    }
}

template <typename T, typename Allocator
void Vector<T, Allocator>::MoveAll(Vector& buffer)
{
    Move(std::begin(*this), std::end(*this), buffer);
}

template <typename T, typename Allocator>
void Vector<T, Allocator>::Rollback(Vector& other, iterator last) noexcept
{
    if (!std::is_nothrow_move_constructible_v<T> &&
        std::is_copy_constructible_v<T>)
    {
        return;
    }
    std::move(std::begin(other), last, std::begin(*this));
}

template <typename T, typename Allocator>
void Vector<T, Allocator>::Commit(Vector&& other) noexcept
{
    this->Deallocate();
    elements = other.elements;
    buffer_capacity = other.buffer_capacity;
    buffer_size = other.buffer_size;
    allocator = other.allocator;
    other.elements = nullptr;
    other.buffer_capacity = 0;
    other.buffer_size = 0;
}

我看到這個代碼有2個問題。 我試圖遵循std::move_if_noexcept邏輯,但是如果元素不是移動可構造但是allocator_traits::construct會引發異常,例如,在自定義分配器中的一些日志代碼? 然后我的MoveAll調用將拋出並僅產生基本保證。 這是標准中的缺陷嗎? 是否應該對Allocator::construct有更嚴格的措辭?

還有一個在Rollback 只有當移動的元素不能移動時,它才真正產生強有力的保證。 否則,再次,只有基本保證。 這是怎么回事?

基於范圍的std::move/copy函數無法提供強大的異常保證。 如果發生異常,您需要一個迭代器來處理成功復制/移動的最后一個元素,以便您可以正確撤消。 您必須手動復制/移動(或編寫專門的功能)。

至於你的問題的細節,標准並沒有真正解決如果construct發出一個異常而不會從構造對象的構造函數中拋出的情況會發生什么。 標准的目的(我將在下面解釋的原因)可能是這種情況永遠不會發生。 但我還沒有在標准中找到任何關於此的陳述。 因此,讓我們暫時假設這是可能的。

為了分配感知容器能夠提供強異常保證, construct起碼構造對象不能扔。 畢竟,你不知道拋出了什么異常,所以否則你將無法判斷對象是否成功構建。 這將使實現標准所需行為變得不可能。 因此,讓我們假設用戶沒有做過讓實現無法實現的事情。

鑒於這種情況,您可以編寫代碼,假設construct發出的任何異常都意味着未構造對象。 如果construct發出異常,盡管給出了可以調用noexcept構造函數的參數,那么你假設構造函數從未被調用過。 然后你相​​應地編寫代碼。

在復制的情況下,您只需要刪除任何已經復制的元素(當然以相反的順序)。 移動案例有點棘手,但仍然相當可行。 您必須移動 - 將每個成功移動的對象分配回其原始位置。

問題? vector<T>::*_back不要求T為MoveAssignable。 它只需要T是Move Insertable :也就是說,你可以使用一個分配器在未初始化的內存中構造它們。 但是你沒有把它移到未初始化的記憶中; 你需要把它移動到已經存在移動T 因此,為了保留這個要求,你需要銷毀所有成功移動的T ,然后將它們MoveInsert到位。

但是因為MoveInsertion需要使用construct ,而之前建立的construct可能會拋出... oops 實際上,這非常簡單, 為什么 vector的重新分配函數不會移動, 除非類型不可移動或不可復制(如果是后一種情況, 則不會獲得強異常保證)。

因此,對我來說,似乎很清楚任何分配器的construct方法都是標准所期望的,只有在所選構造函數拋出時才拋出。 沒有其他方法可以在vector實現所需的行為。 但鑒於沒有明確說明這一要求,我會說這是標准中的缺陷。 這並不是一個新的缺陷,因為我查看了C ++ 17標准而不是工作文件。

顯然, 自2014年以來 ,這一直是LWG問題的主題,其解決方案......很麻煩。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM