2012-12-11 19 views
5

Gần đây tôi đã bắt đầu tìm hiểu về loại tẩy xoá. Hóa ra là kỹ thuật này có thể đơn giản hóa cuộc sống của tôi. Vì vậy, tôi đã cố gắng thực hiện mô hình này. Tuy nhiên, tôi gặp một số vấn đề với copy- và move-constructor của lớp tẩy xóa kiểu. Bây giờ, cho phép đầu tiên có một cái nhìn vào mã, mà là khá thẳng về phía trướcC++ loại tẩy xoá với bản sao mẫu và di chuyển constructor

#include<iostream> 
class A //first class 
{ 
    private: 
     double _value; 
    public: 
     //default constructor 
     A():_value(0) {} 
     //constructor 
     A(double v):_value(v) {} 
     //copy constructor 
     A(const A &o):_value(o._value) {} 
     //move constructor 
     A(A &&o):_value(o._value) { o._value = 0; } 

     double value() const { return _value; } 
}; 

class B //second class 
{ 
    private: 
     int _value; 
    public: 
     //default constructor 
     B():_value(0) {} 
     //constructor 
     B(int v):_value(v) {} 
     //copy constructor 
     B(const B &o):_value(o._value) {} 
     //move constructor 
     B(B &&o):_value(o._value) { o._value = 0; } 

     //some public member 
     int value() const { return _value; } 
}; 

class Erasure //the type erasure 
{ 
    private: 
     class Interface //interface of the holder 
     { 
      public: 
       virtual double value() const = 0; 
     }; 

     //holder template - implementing the interface 
     template<typename T> class Holder:public Interface 
     { 
      public: 
       T _object; 
      public: 
       //construct by copying o 
       Holder(const T &o):_object(o) {} 
       //construct by moving o 
       Holder(T &&o):_object(std::move(o)) {} 
       //copy constructor 
       Holder(const Holder<T> &o):_object(o._object) {} 
       //move constructor 
       Holder(Holder<T> &&o):_object(std::move(o._object)) {} 

       //implements the virtual member function 
       virtual double value() const 
       { 
        return double(_object.value()); 
       } 
     }; 

     Interface *_ptr; //pointer to holder 
    public: 
     //construction by copying o 
     template<typename T> Erasure(const T &o): 
      _ptr(new Holder<T>(o)) 
     {} 

     //construction by moving o 
     template<typename T> Erasure(T &&o): 
      _ptr(new Holder<T>(std::move(o))) 
     {} 

     //delegate 
     double value() const { return _ptr->value(); } 
}; 

int main(int argc,char **argv) 
{ 
    A a(100.2344); 
    B b(-100); 

    Erasure g1(std::move(a)); 
    Erasure g2(b); 

    return 0; 
} 

Như một trình biên dịch tôi sử dụng gcc 4.7 trên một hệ thống kiểm tra Debian. Giả sử mã được lưu trữ trong một file có tên terasure.cpp xây dựng dẫn đến thông báo lỗi sau

$> g++ -std=c++0x -o terasure terasure.cpp 
terasure.cpp: In instantiation of ‘class Erasure::Holder<B&>’: 
terasure.cpp:78:45: required from ‘Erasure::Erasure(T&&) [with T = B&]’ 
terasure.cpp:92:17: required from here 
terasure.cpp:56:17: error: ‘Erasure::Holder<T>::Holder(T&&) [with T = B&]’ cannot be overloaded 
terasure.cpp:54:17: error: with ‘Erasure::Holder<T>::Holder(const T&) [with T = B&]’ 
terasure.cpp: In instantiation of ‘Erasure::Erasure(T&&) [with T = B&]’: 
terasure.cpp:92:17: required from here 
terasure.cpp:78:45: error: no matching function for call to ‘Erasure::Holder<B&>::Holder(std::remove_reference<B&>::type)’ 
terasure.cpp:78:45: note: candidates are: 
terasure.cpp:60:17: note: Erasure::Holder<T>::Holder(Erasure::Holder<T>&&) [with T = B&] 
terasure.cpp:60:17: note: no known conversion for argument 1 from ‘std::remove_reference<B&>::type {aka B}’ to ‘Erasure::Holder<B&>&&’ 
terasure.cpp:58:17: note: Erasure::Holder<T>::Holder(const Erasure::Holder<T>&) [with T = B&] 
terasure.cpp:58:17: note: no known conversion for argument 1 from ‘std::remove_reference<B&>::type {aka B}’ to ‘const Erasure::Holder<B&>&’ 
terasure.cpp:54:17: note: Erasure::Holder<T>::Holder(const T&) [with T = B&] 
terasure.cpp:54:17: note: no known conversion for argument 1 from ‘std::remove_reference<B&>::type {aka B}’ to ‘B&’ 

Dường như cho Erasure g2(b); trình biên dịch vẫn cố gắng sử dụng các nhà xây dựng di chuyển. Đây có phải là hành vi dự định của trình biên dịch không? Tôi có hiểu nhầm một điều gì đó nói chung với kiểu tẩy xóa kiểu không? Có ai đó có một ý tưởng làm thế nào để có được quyền này?

+1

Bạn đã viết sai chính tả tại đó –

Trả lời

3

Hiển nhiên từ lỗi trình biên dịch, trình biên dịch đang cố gắng tạo lớp học Holder cho T = B& của bạn. Điều này có nghĩa là lớp sẽ lưu trữ một thành viên của một kiểu tham chiếu, cho bạn một số vấn đề trên bản sao và như vậy.

Vấn đề nằm trong thực tế là T&& (đối với các đối số mẫu được suy luận) là tham chiếu phổ quát, có nghĩa là nó sẽ liên kết với mọi thứ. Đối với r-giá trị của B nó sẽ suy TB và ràng buộc như một tài liệu tham khảo r có giá trị, cho l-giá trị nó sẽ suy TB& và sử dụng tài liệu tham khảo sụp đổ để giải thích B& && như B& (ví const B l-giá trị nó sẽ khấu trừ T thành const B& và thực hiện thu gọn). Trong ví dụ của bạn b là giá trị l có thể sửa đổi, làm cho hàm tạo tham gia T&& (được giả định là B&) một kết quả phù hợp hơn thì const T& (được suy ra là const B&) một. Điều này cũng có nghĩa là nhà xây dựng Erasure dùng const T& không thực sự cần thiết (không giống như đối với Holder do T không được suy luận cho hàm tạo đó).

Giải pháp cho điều này là xóa tham chiếu (và có thể là constness, trừ khi bạn muốn một thành viên const) từ loại khi tạo lớp chủ của bạn. Bạn cũng nên sử dụng std::forward<T> thay vì std::move, vì như đã đề cập, hàm tạo cũng liên kết với các giá trị l và di chuyển từ những giá trị đó có lẽ là một ý tưởng tồi.

template<typename T> Erasure(T&& o): 
     _ptr(new Holder<typename std::remove_cv<typename std::remove_reference<T>::type>::type>(std::forward<T>(o)) 
    {} 

Có một lỗi trong lớp Erasure của bạn, mà sẽ không bị bắt bởi trình biên dịch: Bạn lưu trữ của bạn Holder trong một con trỏ thô để nhớ heap phân bổ, nhưng có không destructor tùy chỉnh để xóa nó cũng không xử lý tùy chỉnh để sao chép/di chuyển/chuyển nhượng (Quy tắc ba/năm). Một tùy chọn để giải quyết điều đó là triển khai các hoạt động đó (hoặc cấm các hoạt động không cần thiết bằng cách sử dụng =delete). Tuy nhiên điều này hơi tẻ nhạt, vì vậy đề xuất cá nhân của tôi sẽ không quản lý bộ nhớ theo cách thủ công, nhưng sử dụng std::unique_ptr để quản lý bộ nhớ (sẽ không cho bạn khả năng sao chép, nhưng nếu bạn muốn trước tiên bạn cần mở rộng lớp Holder để nhân bản anyways).

điểm khác cần xem xét: Tại sao các bạn thực hiện sao chép tùy chỉnh/nhà thầu di chuyển cho Erasure::Holder<T>, AB? Những cái mặc định sẽ hoàn toàn ổn và sẽ không vô hiệu hóa việc tạo một toán tử gán di chuyển.

điểm khác là Erasure(T &&o) là có vấn đề ở chỗ nó sẽ cạnh tranh với các nhà xây dựng sao chép/di chuyển (T&& thể liên kết với Èrasure& mà là một trận đấu tốt hơn sau đó cả hai const Erasure&Erasure&&). Để tránh điều này, bạn có thể sử dụng enable_if để kiểm tra đối với các loại Erasure, đem lại cho bạn một cái gì đó tương tự như sau:

template<typename T, typename Dummy = typename std::enable_if<!std::is_same<Erasure, std::remove_reference<T>>::value>::type> 
    Erasure(T&& o): 
     _ptr(new Holder<typename std::remove_cv<typename std::remove_reference<T>::type>::type>(std::forward<T>(o)) 
    {} 
+0

Xin lỗi vì trả lời trễ của tôi và cảm ơn lời khuyên của bạn. –

1

Vấn đề của bạn là loại T được suy luận là một tài liệu tham khảo bởi constructor của bạn tham gia một tài liệu tham khảo phổ quát. Bạn muốn sử dụng một cái gì đó dọc theo dòng này:

#include <type_traits> 

class Erasure { 
    .... 

    //construction by moving o 
    template<typename T> 
    Erasure(T &&o): 
     _ptr(new Holder<typename std::remove_reference<T>::type>(std::forward<T>(o))) 
    { 
    } 
}; 

Đó là, bạn cần phải loại bỏ bất kỳ tài liệu tham khảo suy luận từ T (và có lẽ cũng bất kỳ vòng loại cv nhưng việc sửa chữa không làm điều đó). và sau đó bạn không muốn std::move() đối số o nhưng std::forward<T>() nó: sử dụng std::move(o) có thể có hậu quả thảm khốc trong trường hợp bạn thực sự chuyển một tham chiếu không const đến một hàm tạo Erasure.

Tôi không chú ý quá nhiều đến mã khác được đặt xa như tôi có thể nói cũng có một số lỗi ngữ nghĩa (ví dụ, bạn cần một số dạng tham chiếu hoặc dạng clone() int các con trỏ có chứa, cũng như kiểm soát tài nguyên (ví dụ: hàm khởi tạo sao chép, gán bản sao và hàm hủy) trong Erasure.

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