簡體   English   中英

如果std :: move將導致意外的副本,則強制編譯時錯誤?

[英]Force a compile time error if std::move will result in an unintended copy?

在2013年的GoingNative演講中,Scott Meyers指出std::move並不能保證生成的代碼實際上會執行一次移動。

例:

void foo(std::string x, const std::string y) {
  std::string x2 = std::move(x); // OK, will be moved
  std::string y2 = std::move(y); // compiles, but will be copied
}

這里,不能應用移動構造函數,但由於重載分辨率,將使用普通的復制構造函數。 這個后備選項對於向后兼容C ++ 98代碼可能是至關重要的,但在上面的例子中,它很可能不是程序員想要的。

有沒有辦法強制調用移動構造函數?

例如,假設您要移動一個巨大的矩陣。 如果您的應用程序確實依賴於要移動的Matrix,那么如果無法移動則立即獲得編譯錯誤會很棒。 (否則,您可能會通過單元測試輕松地解決性能問題,並且只能在進行一些分析后才能發現。)

讓我們稱之為保證移動strict_move 我希望能夠編寫這樣的代碼:

void bar(Matrix x, const Matrix y) {
  Matrix x2 = strict_move(x); // OK
  Matrix y2 = strict_move(y); // compile error
}

可能嗎?

編輯:

謝謝你的答案! 有一些合理的要求澄清我的問題:

  • 如果輸入是const, strict_move會失敗嗎?
  • 如果結果不會導致實際的移動操作,則strict_move會失敗strict_move (即使副本可能與移動一樣快,例如, const complex<double> )?
  • 都?

我最初的想法非常模糊:我認為Scott Meyers的例子非常令人擔憂,所以我想知道是否有可能讓編譯器阻止這種非預期的副本。

Scott Meyers在他的演講中提到,一般編譯器警告不是一種選擇,因為它會導致大量的誤報。 相反,我想與編譯器進行通信,例如“我100%確定這必須始終導致移動操作,並且副本對於此特定類型來說太昂貴”。

因此,我會strict_move地說,在兩種情況下, strict_move失敗。 與此同時,我不確定什么是最好的。 我沒有考慮的另一個方面是noexcept

在我看來, strict_move的確切語義是開放的。 在編譯時有助於防止一些愚蠢錯誤而沒有嚴重缺點的一切都很好。

我建議不要寫一個檢測const的一般strict_move 我認為這不是你真正想要的。 你想要這個標記一個const complex<double> ,還是一個const pair<int, int> 這些類型將在移動時快速復制。 標記它們只會是一種刺激。

如果你想這樣做,我建議改為查看類型是否為noexcept MoveConstructible 這對std::string 如果意外調用了string的復制構造函數,則它不是noexcept,因此將被標記。 但如果意外調用pair<int, int>的復制構造函數,你真的關心嗎?

這是一個草圖,它的外觀如下:

#include <utility>
#include <type_traits>

template <class T>
typename std::remove_reference<T>::type&&
noexcept_move(T&& t)
{
    typedef typename std::remove_reference<T>::type Tr;
    static_assert(std::is_nothrow_move_constructible<Tr>::value,
                  "noexcept_move requires T to be noexcept move constructible");
    static_assert(std::is_nothrow_move_assignable<Tr>::value,
                  "noexcept_move requires T to be noexcept move assignable");
    return std::move(t);
}

我決定檢查is_nothrow_move_assignable ,因為你不知道客戶端是在構建還是分配lhs。

我選擇了內部static_assert而不是外部enable_if因為我不希望noexcept_move被重載,並且static_assert會在觸發時產生更清晰的錯誤消息。

首先,我想說明你以前的答案不會完全解決你的問題。

以前的解決方案(@Kerrerk和@ 0x499602D2)失敗的反例:假設您使用拋出異常的移動構造函數編寫了矩陣類。 現在假設您要移動std::vector<matrix> 這篇文章表明,如果std::vector<matrix>保存的矩陣類元素實際被移動(如果第j個元素移動構造函數拋出異常會發生什么?你會丟失數據),你不能擁有“強異常保證”因為沒有辦法恢復你已經移動的元素!)。

這就是為什么stl容器使用std::move_if_noexcept實現.push_back() .reserve()及其移動構造std::move_if_noexcept來移動它們所持有的元素。 以下是從open-std獲取的reserve()的示例實現:

void reserve(size_type n)
{
    if (n > this->capacity())
    {
        pointer new_begin = this->allocate( n );
        size_type s = this->size(), i = 0;
        try
        {
            for (;i < s; ++i)
                 new ((void*)(new_begin + i)) value_type( std::move_if_noexcept( (*this)[i]) ) );
        }
        catch(...)
        {
            while (i > 0)                 // clean up new elements
               (new_begin + --i)->~value_type();

            this->deallocate( new_begin );    // release storage
            throw;
        }
        // -------- irreversible mutation starts here -----------
        this->deallocate( this->begin_ );
        this->begin_ = new_begin;
        this->end_ = new_begin + s;
        this->cap_ = new_begin + n;
    }
}

所以,如果你沒有強制你的move和默認構造函數是noexcept,你不能保證像std :: vector.resize()或std :: move(對於stl容器)這樣的函數永遠不會復制矩陣類和這是正確的行為(否則你可能會丟失數據)

您可以創建自己的move版本,不允許常量返回類型。 例如:

#include <utility>
#include <string>
#include <type_traits>


template <typename T>
struct enforce_nonconst
{
    static_assert(!std::is_const<T>::value, "Trying an impossible move");
    typedef typename std::enable_if<!std::is_const<T>::value, T>::type type;
};

template <typename T>
constexpr typename enforce_nonconst<typename std::remove_reference<T>::type>::type &&
mymove(T && t) noexcept
{
    return static_cast<typename std::remove_reference<T>::type &&>(t);
}

void foo(std::string a, std::string const b)
{
    std::string x = std::move(a);
    std::string y = std::move(b);
}

void bar(std::string a, std::string const b)
{
    std::string x = mymove(a);
    // std::string y = mymove(b);  // Error
}

int main() { }

暫無
暫無

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

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