2017-11-17 25 views
7

Tôi có một lớp giống như vùng chứa, mà tôi muốn di chuyển chỉ khi kiểu cơ bản là chỉ di chuyển, nhưng có thể sao chép được. Để đơn giản, chúng ta hãy giả copyability được xác định bởi một đơn bool mẫu tham số:Thêm hàm tạo bản sao dựa trên thông số mẫu

template<bool IsCopyable> 
struct Foo 
{ 
    Foo(); 
    Foo(const Foo&); // only include this when IsCopyable is true 
    Foo(Foo&&); 

    Foo& operator=(const Foo&); // only when IsCopyable 
    Foo& operator=(Foo&&); 
}; 

Bây giờ, tôi không thể chỉ SFINAE bản sao ctor đi, bởi vì đó đòi hỏi làm cho nó templated và một chức năng templated không thể là một ctor bản sao. Ngoài ra, tôi không thể chỉ làm một static_assert() trong ctor sao chép. Mặc dù điều này sẽ bắt gặp việc sử dụng sai lầm của ctor bản sao, nó cũng làm cho lớp vốn đã sao chép có thể xây dựng từ bên ngoài (đặc điểm loại std::is_copy_constructible sẽ mang lại giá trị đúng). Bằng cách này, một yêu cầu không may là nó cần phải biên dịch trong VC++ 2012, vì vậy tôi không thể sử dụng biểu thức ưa thích SFINAE, hàm ctors thừa kế, hàm mặc định/đã xóa hoặc constexpr if (tuy nhiên, nếu bạn có một giải pháp gọn gàng cho C++ 17 Tôi vẫn muốn nghe nó :))

Phương pháp hiển nhiên là sử dụng chuyên môn mẫu. Tôi không muốn đi tuyến đường này, bởi vì trong thực tế Foo có khá nhiều chức năng và tôi không muốn lặp lại bản thân mình. Tuy nhiên, điều này có vẻ là lựa chọn duy nhất của tôi và tôi có thể triển khai một số chia sẻ mã bằng cách sử dụng lớp cơ sở, như vậy:

// Base functionality 
template<bool IsCopyable> 
struct FooBase 
{ 
    FooBase(); 

    // move ctor and assignment can go here 
    FooBase(FooBase&&);  
    FooBase& operator=(FooBase&&); 

    // some generic conversion ctor and assignment that I happen to need 
    template<class T> FooBase(T&& t); 
    template<class T> FooBase& operator=(T&&); 

    // ... all sorts of functionality and datamembers  
}; 

// Foo<false> 
template<bool IsCopyable> 
struct Foo : FooBase<IsCopyable> 
{ 
    // can't use inheriting ctors in VS 2012, wrap the calls manually: 
    Foo() { } 
    Foo(Foo&& other) : FooBase<IsCopyable>(std::move(other)) { } 
    Foo& operator=(Foo&& other) 
    { 
     FooBase<IsCopyable>::operator=(std::move(other)); 
     return *this; 
    } 

    template<class T> Foo(T&& t) : FooBase<IsCopyable>(std::forward<T>(t)) { } 
    template<class T> Foo& operator=(T&& t) 
    { 
     FooBase<IsCopyable>::operator=(std::forward<T>(t)); 
     return *this; 
    } 
}; 

// Foo<true> 
template<> 
struct Foo<true> : FooBase<true> 
{ 
    // add these 
    Foo(const Foo&); 
    Foo& operator=(const Foo&); 

    // wrapping calls because of VS 2012: 
    Foo() { } 
    Foo(Foo&& other) : FooBase<true>(std::move(other)) { } 
    Foo& operator=(Foo&& other) 
    { 
     FooBase<true>::operator=(std::move(other)); 
     return *this; 
    } 

    template<class T> Foo(T&& t) : FooBase<true>(std::forward<T>(t)) { } 
    template<class T> Foo& operator=(T&& t) 
    { 
     FooBase<true>::operator=(std::forward<T>(t)); 
     return *this; 
    } 
}; 

Vẫn còn một chút. May mắn thay nó được sạch hơn một khi bạn có thể sử dụng ctors kế thừa và chức năng mặc định. Tuy nhiên, tôi đã hy vọng có một cách đơn giản hơn, lý tưởng là không sử dụng một lớp cơ sở.

Trả lời

9
struct nonesuch { 
private: 
    ~nonesuch(); 
    nonesuch(const nonesuch&); 
    void operator=(const nonesuch&); 
}; 

template<bool IsCopyable> 
struct Foo { 
    Foo(const typename std::conditional<IsCopyable, Foo, nonesuch>::type& other) { 
     // copy ctor impl here 
    } 
private: 
    Foo(const typename std::conditional<!IsCopyable, Foo, nonesuch>::type&); 
}; 

Tương tự như vậy để gán.

+0

Wow! Đơn giản và thanh lịch. Tôi phải nhớ thủ thuật này. – max66

+0

Giải pháp tuyệt vời ngoại trừ việc thành ngữ "không có định nghĩa riêng" thành ngữ đã lỗi thời trong C++ 11. Tôi đã xóa những phương pháp này một cách rõ ràng. –

+0

@ArneVogel OP cho biết họ không thể sử dụng các chức năng đã xóa. –

0

Tôi không biết nếu có thể làm việc với VC++ của bạn 2012 nhưng ... nếu tôi nhớ chính xác, có một phương pháp mà tôi nghĩ tốt hơn một chút mà kế thừa từ FooBase.

Một loại tự động kế thừa.

Bạn có thể khai báo struct

template <bool> 
struct Bar; 

và chuyên phiên bản true với tất cả các nhà xây dựng/nhà khai thác

template <> 
struct Bar<true> 
{ 
    Bar() 
    { std::cout << "Bar()" << std::endl; }; 

    Bar (Bar &&) 
    { std::cout << "Bar (Bar &&)" << std::endl; } 

    Bar (Bar const &) 
    { std::cout << "Bar (Bar const &)" << std::endl; } 

    Bar & operator= (Bar &&) 
    { std::cout << "operator= (Bar &&)" << std::endl; return *this; } 

    Bar & operator= (Bar const &) 
    { std::cout << "operator= (Bar const &)" << std::endl; return *this; } 
}; 

Bây giờ bạn có thể chuyên phiên bản false kế thừa từ Bar<true>, xác nhận (= default) những gì bạn muốn và xóa (= delete) bạn không muốn gì; cái gì đó như

template <> 
struct Bar<false> : public Bar<true> 
{ 
    using Bar<true>::Bar; 

    // confirmed constructor/operators 
    Bar()     = default; 
    Bar (Bar &&)    = default; 
    Bar & operator= (Bar &&) = default; 

    // deleted constructor/operators 
    Bar (Bar const &)    = delete; 
    Bar & operator= (Bar const &) = delete; 
}; 

Sau đây là một ví dụ làm việc toàn

#include <iostream> 

template <bool> 
struct Bar; 

template <> 
struct Bar<true> 
{ 
    Bar() 
    { std::cout << "Bar()" << std::endl; }; 

    Bar (Bar &&) 
    { std::cout << "Bar (Bar &&)" << std::endl; } 

    Bar (Bar const &) 
    { std::cout << "Bar (Bar const &)" << std::endl; } 

    Bar & operator= (Bar &&) 
    { std::cout << "operator= (Bar &&)" << std::endl; return *this; } 

    Bar & operator= (Bar const &) 
    { std::cout << "operator= (Bar const &)" << std::endl; return *this; } 
}; 

template <> 
struct Bar<false> : public Bar<true> 
{ 
    using Bar<true>::Bar; 

    // confirmed constructor/operators 
    Bar()     = default; 
    Bar (Bar &&)    = default; 
    Bar & operator= (Bar &&) = default; 

    // deleted constructor/operators 
    Bar (Bar const &)    = delete; 
    Bar & operator= (Bar const &) = delete; 
}; 


int main() 
{ 
    Bar<true> bt1; 
    Bar<false> bf1; 

    auto bt2 = bt1; 
    // auto bf2 = bf1; compilation error (copy constructor deleted) 

    auto bt3 = std::move(bt1); 
    auto bf3 = std::move(bf1); 

    Bar<true> bt4; 
    Bar<false> bf4; 

    bt4 = bt3; 
    // bf4 = bf3; compilation error (operator= (Bar const &) deleted) 

    bt3 = std::move(bt4); 
    bf3 = std::move(bf4); 
} 
3

bạn không thể sử dụng một lớp cơ sở để chọn lọc vô hiệu hóa sao chép? Bằng cách đó bạn không cần phải lặp lại bất kỳ chức năng khác của lớp chính:

template <bool b> 
struct MaybeCopyable {}; 

template <> 
struct MaybeCopyable<false> { 
    MaybeCopyable(const MaybeCopyable&) = delete; 
    MaybeCopyable& operator=(const MaybeCopyable&) = delete; 
}; 

template<bool IsCopyable> 
struct Foo : MaybeCopyable<IsCopyable> { 
    // other functionality 
}; 

Nếu MaybeCopyable<false> là một trong các lớp cơ sở, các nhà xây dựng bản sao của Foo sẽ được tự động xóa.

Các vấn đề liên quan