2009-02-12 33 views
12

Tôi đang cố gắng viết một phân bổ STL tùy chỉnh có nguồn gốc từ std::allocator, nhưng bằng cách nào đó tất cả các cuộc gọi đến allocate() chuyển đến lớp cơ sở. Tôi đã thu hẹp nó xuống mã này:Tại sao bộ phân bổ C++ STL này không phân bổ?

template <typename T> class a : public std::allocator<T> { 
public: 
    T* allocate(size_t n, const void* hint = 0) const { 
     cout << "yo!"; 
     return 0; 
    } 
}; 

int main() 
{ 
    vector<int, a<int>> v(1000, 42); 
    return 0; 
} 

Tôi mong đợi "Yo!" để được in, tiếp theo là một số lỗi khủng khiếp bởi vì tôi không thực sự phân bổ bất cứ điều gì. Thay vào đó, chương trình chạy tốt và không in được gì. Tôi đang làm gì sai?

Tôi nhận được kết quả tương tự trong gcc và VS2008.

+0

liên quan, xem [Tại sao không để thừa kế từ std :: cấp phát ] (http: // stackoverflow.com/q/21081796). – jww

Trả lời

6

Bạn sẽ cần phải cung cấp mẫu thành viên rebind và các nội dung khác được liệt kê trong yêu cầu cấp phát trong tiêu chuẩn C++. Ví dụ: bạn cần một hàm tạo bản sao mẫu không chỉ chấp nhận allocator<T> mà còn chấp nhận allocator<U>. Ví dụ, một mã có thể làm, mà một std :: danh sách ví dụ là có khả năng làm

template<typename Allocator> 
void alloc1chunk(Allocator const& alloc) { 
    typename Allocator::template rebind< 
     wrapper<typename Allocator::value_type> 
     >::other ot(alloc); 
    // ... 
} 

Mã sẽ thất bại nếu có một trong hai tồn tại không có mẫu rebind chính xác, hoặc có tồn tại không có constructor sao chép tương ứng. Bạn sẽ nhận được hư không hữu ích với đoán những gì các yêu cầu được. Sớm hay muộn bạn sẽ phải làm với mã dựa trên một phần của những yêu cầu cấp phát, và mã sẽ thất bại bởi vì người cấp phát của bạn vi phạm chúng. Tôi khuyên bạn nên xem chúng trong một số bản thảo làm việc của bạn bản sao của tiêu chuẩn trong 20.1.5.

+0

Điểm tốt về việc đọc các yêu cầu giao diện thực tế - nếu không bạn sẽ không bao giờ biết * chắc chắn * rằng bạn đang xử lý mọi thứ mà người cấp phát phải làm. –

1

Mã sau in "yo" như mong đợi - những gì bạn thấy là người bạn cũ "hành vi không xác định" của chúng tôi.

#include <iostream> 
#include <vector> 
using namespace std; 

template <typename T> class a : public std::allocator<T> { 
public: 
    T* allocate(size_t n, const void* hint = 0) const { 
     cout << "yo!"; 
     return new T[10000]; 
    } 
}; 

int main() 
{ 
    vector<int, a<int> > v(1000, 42); 
    return 0; 
} 

Chỉnh sửa: Tôi vừa xem tiêu chuẩn C++ về trình phân bổ mặc định. Không có cấm thừa kế từ nó. Trong thực tế, theo như tôi biết, không có cấm như vậy trong bất kỳ phần nào của Tiêu chuẩn.

+0

Điều này không hiệu quả đối với tôi. VS 2008. –

+0

Bạn đã kiểm tra mã này chưa? Nó không thể làm việc vì nó cơ bản giống với câu hỏi. Tôi chỉ cần chạy nó trong chế độ gỡ lỗi và phát hành và nó không hoạt động. – Klaim

+0

Hoạt động tốt cho tôi với i686-apple-darwin8-g ++ - 4.0.1 –

4

Trong trường hợp này, sự cố là tôi không ghi đè thành viên rebind của người cấp phát. Phiên bản này hoạt động (trong VS2008):

template <typename T> class a : public std::allocator<T> { 
public: 
    T* allocate(size_t n, const void* hint = 0) const { 
     cout << "yo!"; 
     return 0; 
    } 

    template <typename U> struct rebind 
    { 
     typedef a<U> other; 
    }; 
}; 

int main() { 
    vector<int, a<int>> v(1000, 42); 
    return 0; 
} 

Tôi tìm thấy điều này bằng cách gỡ lỗi thông qua các tiêu đề STL.

Dù việc này có hoạt động hay không sẽ hoàn toàn phụ thuộc vào việc thực hiện STL, vì vậy tôi nghĩ rằng cuối cùng, Klaim ở ngay trong điều này không nên thực hiện theo cách này.

+1

Trên thực tế chức năng ảo gửi không được sử dụng cho các cuộc gọi cấp phát kể từ khi loại được biết đến lúc biên dịch thời gian, vì vậy tôi tin rằng điều này nên bây giờ luôn luôn làm việc. Lý do duy nhất không phải bận tâm kế thừa từ std :: allocator là bởi vì nó không thực sự giúp bạn tiết kiệm nhiều đánh máy! –

+0

... nhưng xem câu trả lời của litb cho một yêu cầu khác (mẫu bản sao ctor) mà bạn cần phải thực hiện. –

2

Tôi có hai mẫu để tạo các trình phân bổ tùy chỉnh; các công trình đầu tiên automagically nếu nó được sử dụng trên một kiểu tùy chỉnh:

template<> 
class std::allocator<MY_TYPE> 
{ 
public: 
    typedef size_t  size_type; 
    typedef ptrdiff_t difference_type; 
    typedef MY_TYPE* pointer; 
    typedef const MY_TYPE* const_pointer; 
    typedef MY_TYPE& reference; 
    typedef const MY_TYPE& const_reference; 
    typedef MY_TYPE  value_type; 

    template <class U> 
    struct rebind 
    { 
     typedef std::allocator<U> other; 
    }; 

    pointer allocate(size_type n, std::allocator<void>::const_pointer hint = 0) 
    { 
     return reinterpret_cast<pointer>(ALLOC_FUNC(n * sizeof(T))); 
    } 
    void construct(pointer p, const_reference val) 
    { 
     ::new(p) T(val); 
    } 
    void destroy(pointer p) 
    { 
     p->~T(); 
    } 
    void deallocate(pointer p, size_type n) 
    { 
     FREE_FUNC(p); 
    } 
    size_type max_size() const throw() 
    { 
     // return ~size_type(0); -- Error, fixed according to Constantin's comment 
     return std::numeric_limits<size_t>::max()/sizeof(MY_TYPE); 
    } 
}; 

Thứ hai được sử dụng khi chúng ta muốn có cấp phát riêng của chúng tôi cho một loại được xác định trước với một cấp tiêu chuẩn, ví dụ char, wchar_t, std :: chuỗi, v.v.:

namespace MY_NAMESPACE 
    { 

    template <class T> class allocator; 

    // specialize for void: 
    template <> 
    class allocator<void> 
    { 
    public: 
     typedef void*  pointer; 
     typedef const void* const_pointer; 
     // reference to void members are impossible. 
     typedef void  value_type; 

     template <class U> 
     struct rebind 
     { 
      typedef allocator<U> other; 
     }; 
    }; 

    template <class T> 
    class allocator 
    { 
    public: 
     typedef size_t  size_type; 
     typedef ptrdiff_t difference_type; 
     typedef T*  pointer; 
     typedef const T* const_pointer; 
     typedef T&  reference; 
     typedef const T& const_reference; 
     typedef T  value_type; 

     template <class U> 
     struct rebind 
     { 
      typedef allocator<U> other; 
     }; 

     allocator() throw() 
     { 
     } 
     template <class U> 
     allocator(const allocator<U>& u) throw() 
     { 
     } 
     ~allocator() throw() 
     { 
     } 

     pointer address(reference r) const 
     { 
      return &r; 
     } 
     const_pointer address(const_reference r) const 
     { 
      return &r; 
     } 
     size_type max_size() const throw() 
     { 
      // return ~size_type(0); -- Error, fixed according to Constantin's comment 
      return std::numeric_limits<size_t>::max()/sizeof(T); 
     } 
     pointer allocate(size_type n, allocator<void>::const_pointer hint = 0) 
     { 
      return reinterpret_cast<pointer>(ALLOC_FUNC(n * sizeof(T))); 
     } 
     void deallocate(pointer p, size_type n) 
     { 
      FREE_FUNC(p); 
     } 

     void construct(pointer p, const_reference val) 
     { 
      ::new(p) T(val); 
     } 
     void destroy(pointer p) 
     { 
      p->~T(); 
     } 
    }; 

template <class T1, class T2> 
inline 
bool operator==(const allocator<T1>& a1, const allocator<T2>& a2) throw() 
{ 
    return true; 
} 

template <class T1, class T2> 
inline 
bool operator!=(const allocator<T1>& a1, const allocator<T2>& a2) throw() 
{ 
    return false; 
} 

} 

Mẫu đầu tiên ở trên, cho loại được xác định riêng của bạn, không yêu cầu xử lý thêm nhưng được sử dụng tự động bởi các lớp chứa tiêu chuẩn. Mẫu thứ hai yêu cầu làm việc thêm khi được sử dụng trên một loại tiêu chuẩn. Đối với std :: string, ví dụ, một phải sử dụng cấu trúc sau khi khai báo biến kiểu đó (nó là đơn giản nhất với một typedef):

std::basic_string<char>, std::char_traits<char>, MY_NAMESPACE::allocator<char> > 
+0

Dường như 'max_size()' phải trả về 'std :: numeric_limits :: max()/sizeof (MY_TYPE)'. – Constantin

+0

@Constantin: Quan sát rất tốt, cảm ơn rất nhiều! Tôi sẽ cập nhật mã cho phù hợp. :-) –

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