2013-09-11 13 views
8

Cấu trúc sau thất bại trong việc biên dịch trong môi trường C++ 11 do thực tế là tôi đã tuyên bố các nhà điều hành phân công di chuyển như noexcept:Tôi có thể bắt buộc một chức năng thành viên đặc biệt mặc định không được chấp nhận không?

struct foo 
{ 
    std::vector<int> data; 
    foo& operator=(foo&&) noexcept = default; 
}; 

Toán tử gán di chuyển mặc định được tạo ra bởi trình biên dịch là noexcept(false) do thực tế rằng nhiệm vụ di chuyển của std::vector<int> cũng là noexcept(false). Điều này lần lượt là do thực tế là người cấp phát mặc định có std::allocator_traits<T>:: propagate_on_container_move_assignment được đặt thành std::false_type. Xem thêm this question.

Tôi tin rằng điều này đã được khắc phục trong C++ 14 (xem library defect 2103).

Câu hỏi của tôi là, có cách nào để tôi bắt buộc noexcept khi toán tử gán nhiệm vụ chuyển mặc định mà không phải tự định nghĩa nó không?

Nếu điều này là không thể, có cách nào tôi có thể đánh lừa std::vector<int> thành noexcept chuyển nhượng được để noexcept(true) được chuyển qua cấu trúc của tôi không?

+2

Is it okay nếu 'dữ liệu' là một' std :: vector ' trong đó 'A' không phải là mặc định' std :: allocator '? – aschepler

+1

Tôi đang tìm cách giữ độ phức tạp của mã ở mức tối thiểu. Tôi hy vọng rằng việc sử dụng một trình phân bổ tùy chỉnh sẽ khó hơn cho các lập trình viên khác để hiểu hơn một toán tử gán di chuyển không mặc định đơn giản. – marack

+1

Điều đó nói rằng, nếu 'A' trong' std :: vector 'là một trình bao bọc nội tuyến đơn giản cho trình phân bổ mặc định, nó có thể hoạt động ... – marack

Trả lời

4

Tôi tin rằng điều này đã được khắc phục trong C++ 14 (xem lỗi về thư viện 2103).

Khi DR sửa chữa nên được coi là một sửa chữa cho C++ 11 và do đó, một số triển khai C++ 11 sẽ đã sửa nó.

Câu hỏi của tôi là, có cách nào để tôi buộc noexcept khi toán tử gán nhiệm vụ chuyển mặc định mà không phải tự xác định không?

Đối với toán tử gán di chuyển mặc định là noexcept, bạn cần làm cho đối tượng con của nó có noexcept di chuyển toán tử gán.

Cách di động rõ ràng nhất mà tôi có thể nghĩ đến là sử dụng một wrapper xung quanh std::vector mà lực lượng di chuyển được noexcept

template<typename T, typename A = std::allocator<T>> 
    struct Vector : std::vector<T, A> 
    { 
    using vector::vector; 

    Vector& operator=(Vector&& v) noexcept 
    { 
     static_cast<std::vector<T,A>&>(*this) = std::move(v); 
     return *this; 
    } 
    Vector& operator=(const Vector&) = default; 
    }; 

Một tùy chọn khác tương tự là xác định loại cấp phát của riêng bạn với việc sửa chữa DR 2013 và sử dụng:

template<typename T> 
    struct Allocator : std::allocator<T> 
    { 
    Allocator() = default; 
    template<typename U> Allocator(const Allocator<U>&) { } 
    using propagate_on_container_move_assignment = true_type; 
    template<typename U> struct rebind { using other = Allocator<U>; }; 
    }; 

template<typename T> 
    using Vector = std::vector<T, Allocator<T>>; 

một lựa chọn khác là sử dụng một thư viện thực hiện tiêu chuẩn như của GCC mà thực hiện các giải pháp cho DR 2013 và cũng làm cho toán tử gán di chuyển std::vector 's noexcept cho các loại phân bổ khác khi nó được biết rằng tất cả các cá thể cấp phát so sánh bằng nhau.

1

Tôi không nghĩ rằng bạn có thể buộc bất cứ điều gì, nhưng bạn có thể quấn nó:

#include <iostream> 
#include <vector> 

template <typename T> 
struct Wrap 
{ 
    public: 
    Wrap() noexcept 
    { 
     new (m_value) T; 
    } 

    Wrap(const Wrap& other) noexcept 
    { 
     new (m_value) T(std::move(other.value())); 
    } 

    Wrap(Wrap&& other) noexcept 
    { 
     std::swap(value(), other.value()); 
    } 


    Wrap(const T& other) noexcept 
    { 
     new (m_value) T(std::move(other)); 
    } 

    Wrap(T&& other) noexcept 
    { 
     new (m_value) T(std::move(other)); 
    } 

    ~Wrap() noexcept 
    { 
     value().~T(); 
    } 

    Wrap& operator = (const Wrap& other) noexcept 
    { 
     value() = other.value(); 
     return *this; 
    } 

    Wrap& operator = (Wrap&& other) noexcept 
    { 
     value() = std::move(other.value()); 
     return *this; 
    } 

    Wrap& operator = (const T& other) noexcept 
    { 
     value() = other; 
     return *this; 
    } 

    Wrap& operator = (T&& other) noexcept 
    { 
     value() = std::move(other); 
     return *this; 
    } 

    T& value() noexcept { return *reinterpret_cast<T*>(m_value); } 
    const T& value() const noexcept { return *reinterpret_cast<const T*>(m_value); } 
    operator T&() noexcept { return value(); } 
    operator const T&() const noexcept { return value(); } 

    private: 
    typename std::aligned_storage <sizeof(T), std::alignment_of<T>::value>::type m_value[1]; 
}; 


struct Foo 
{ 
    public: 
    Foo& operator = (Foo&&) noexcept = default; 

    std::vector<int>& data() noexcept { return m_data; } 
    const std::vector<int>& data() const noexcept { return m_data; } 

    private: 
    Wrap<std::vector<int>> m_data; 
}; 

int main() { 
    Foo foo; 
    foo.data().push_back(1); 
    Foo boo; 
    boo = std::move(foo); 
    // 01 
    std::cout << foo.data().size() << boo.data().size() << std::endl; 
    return 0; 
} 

(Cảm ơn Jonathan Wakely)

+1

Loại đó không thể sao chép được, bạn cần sao chép ctor và chuyển nhượng. Ngoài ra, bạn có thể sử dụng 'std :: aligned_storage :: type' thay vì mảng char. –