2011-02-08 35 views
10

Tôi tự hỏi liệu có nên bao bọc các thùng chứa C++ STL để duy trì tính nhất quán và có thể hoán đổi việc triển khai mà không sửa đổi mã máy khách.Bao bì chứa đựng để duy trì tính nhất quán

Ví dụ, trong một dự án, chúng tôi sử dụng CamelCase để đặt tên lớp và các hàm thành viên (Foo :: DoSomething()), tôi sẽ quấn std :: danh vào một lớp học như thế này:

template<typename T> 
class List 
{ 
    public: 
     typedef std::list<T>::iterator Iterator; 
     typedef std::list<T>::const_iterator ConstIterator; 
     // more typedefs for other types. 

     List() {} 
     List(const List& rhs) : _list(rhs._list) {} 
     List& operator=(const List& rhs) 
     { 
      _list = rhs._list; 
     } 

     T& Front() 
     { 
      return _list.front(); 
     } 

     const T& Front() const 
     { 
      return _list.front(); 
     } 

     void PushFront(const T& x) 
     { 
      _list.push_front(x); 
     } 

     void PopFront() 
     { 
      _list.pop_front(); 
     } 

     // replace all other member function of std::list. 

    private: 
     std::list<T> _list; 
}; 

Sau đó, tôi sẽ có thể viết một cái gì đó như thế này:

typedef uint32_t U32; 
List<U32> l; 
l.PushBack(5); 
l.PushBack(4); 
l.PopBack(); 
l.PushBack(7); 

for (List<U32>::Iterator it = l.Begin(); it != l.End(); ++it) { 
    std::cout << *it << std::endl; 
} 
// ... 

tôi tin rằng hầu hết các compliers C++ hiện đại có thể tối ưu hóa đi những gián tiếp thêm một cách dễ dàng, và tôi nghĩ rằng phương pháp này có một số ưu điểm như:

Tôi có thể mở rộng chức năng của lớp List một cách dễ dàng. Ví dụ, tôi muốn có một chức năng tốc ký mà sắp xếp danh sách và sau đó gọi duy nhất(), tôi có thể mở rộng nó bằng cách thêm một chức năng thành viên:

template<typename T> 
void List<T>::SortUnique() 
{ 
    _list.sort(); 
    _list.unique(); 
} 

Ngoài ra, tôi có thể trao đổi việc thực hiện cơ bản (nếu cần thiết) mà không cần bất kỳ thay đổi trên mã họ sử dụng Danh sách miễn là hành vi là như nhau. Ngoài ra còn có những lợi ích khác bởi vì nó duy trì tính nhất quán của quy ước đặt tên trong một dự án, vì vậy nó không có push_back() cho STL và PUSHBACK() cho các lớp học khác trên tất cả các dự án như:

std::list<MyObject> objects; 
// insert some MyObject's. 
while (!objects.empty()) { 
    objects.front().DoSomething(); 
    objects.pop_front(); 
    // Notice the inconsistency of naming conventions above. 
} 
// ... 

Tôi tự hỏi nếu phương pháp này có bất kỳ nhược điểm lớn (hoặc nhỏ), hoặc nếu điều này thực sự là một phương pháp thực tế.

Cảm ơn.

CHỈNH SỬA: OK, cảm ơn câu trả lời cho đến nay. Tôi nghĩ rằng tôi có thể đã đặt quá nhiều vào việc đặt tên nhất quán trong câu hỏi. Trên thực tế quy ước đặt tên không phải là mối quan tâm của tôi ở đây, vì người ta có thể cung cấp một giao diện chính xác giống như tốt:

template<typename T> 
void List<T>::pop_back() 
{ 
    _list.pop_back(); 
} 

Hoặc một thậm chí có thể làm cho giao diện thực hiện khác trông giống như một STL rằng hầu hết các lập trình viên C++ đã quen thuộc với. Nhưng dù sao, theo ý kiến ​​của tôi, đó là một điều phong cách và không quan trọng chút nào.

Điều tôi quan tâm là sự nhất quán để có thể dễ dàng thay đổi chi tiết triển khai. Một ngăn xếp có thể được thực hiện theo nhiều cách khác nhau: một mảng và một chỉ mục hàng đầu, một danh sách liên kết hoặc thậm chí là một kết hợp của cả hai, và tất cả chúng đều có đặc tính LIFO của một cấu trúc dữ liệu. Cây tìm kiếm nhị phân tự cân bằng có thể được thực hiện với cây AVL hoặc cây đỏ đen, và cả hai đều có độ phức tạp thời gian trung bình O (logn) để tìm kiếm, chèn và xóa.

Vì vậy, nếu tôi có thư viện cây AVL và một thư viện cây đỏ đen khác với các giao diện khác nhau và tôi sử dụng cây AVL để lưu trữ một số đối tượng. Sau đó, tôi đã tìm ra (bằng cách sử dụng profilers hoặc bất kỳ thứ gì), sử dụng cây đỏ đen sẽ tăng hiệu năng, tôi sẽ phải đi đến mọi phần của các tệp sử dụng cây AVL, và thay đổi tên, tên phương thức và đối số đơn đặt hàng cho các đối tác cây màu đỏ-đen của nó. Có lẽ thậm chí một số kịch bản mà lớp mới chưa có chức năng tương đương được viết. Tôi nghĩ rằng nó cũng có thể giới thiệu các lỗi tinh vi cũng vì sự khác biệt trong việc thực hiện, hoặc tôi mắc lỗi.

Vì vậy, những gì tôi bắt đầu tự hỏi rằng nếu nó là giá trị nguyên cần thiết để duy trì như một lớp bao bọc để che giấu những chi tiết thực hiện và cung cấp một giao diện thống nhất cho triển khai khác nhau:

template<typename T> 
class AVLTree 
{ 
    // ... 
    Iterator Find(const T& val) 
    { 
     // Suppose the find function takes the value to be searched and an iterator 
     // where the search begins. It returns end() if val cannot be found. 
     return _avltree.find(val, _avltree.begin()); 
    } 
}; 

template<typename T> 
class RBTree 
{ 
    // ... 
    Iterator Find(const T& val) 
    { 
     // Suppose the red-black tree implementation does not support a find function, 
     // so you have to iterate through all elements. 
     // It would be a poor tree anyway in my opinion, it's just an example. 
     auto it = _rbtree.begin(); // The iterator will iterate over the tree 
            // in an ordered manner. 
     while (it != _rbtree.end() && *it < val) { 
      ++it; 
     } 
     if (*++it == val) { 
      return it; 
     } else { 
      return _rbtree.end(); 
     } 
    } 
}; 

Bây giờ, tôi chỉ phải đảm bảo rằng AVLTree :: Find() và RBTree :: Find() thực hiện chính xác điều tương tự (ví dụ lấy giá trị cần tìm kiếm, trả về một trình lặp cho phần tử hoặc End(), nếu không). Và sau đó, nếu tôi muốn thay đổi từ một cây AVL để một cây đỏ-đen, tất cả tôi phải làm là thay đổi tuyên bố:

AVLTree<MyObject> objectTree; 
AVLTree<MyObject>::Iterator it; 

tới:

RBTree<MyObject> objectTree; 
RBTree<MyObject>::Iterator it; 

và mọi thứ khác sẽ tương tự, bằng cách duy trì hai lớp.

Ở trên chỉ là một ví dụ về những gì tôi muốn lưu trữ với lớp trình bao bọc. Có lẽ kiểu kịch bản này quá hiếm khi phát triển thực sự và tôi lo lắng quá nhiều, tôi không biết. Đó là lý do tôi hỏi.

Tôi đã thay đổi tiêu đề từ "Bao bì STL Container" thành "Bao gói để duy trì tính nhất quán" để phản ánh vấn đề chính xác hơn.

Cảm ơn thời gian của bạn.

+2

Suy nghĩ về ý tưởng này khiến tôi bị bệnh về thể chất. –

+0

Không thấy bất cứ điều gì sai trái khi thực hiện nó. nhưng bạn có thực sự muốn ??? – Arunmu

+0

Vì vậy, để có được điều này thẳng, bạn muốn làm tất cả điều này chỉ vì lý do đặt tên và trao đổi việc thực hiện? Đặt tên không thể là vì bạn sẽ bọc rất nhiều thứ STL khác. – RvdK

Trả lời

6

Tôi tự hỏi nếu phương pháp này có bất kỳ khó khăn lớn (hoặc nhỏ),

Hai chữ: Bảo trì cơn ác mộng.

Và sau đó, khi bạn nhận được trình biên dịch C++ 0x cho phép di chuyển mới, bạn sẽ phải mở rộng tất cả các lớp trình bao bọc của mình.

Đừng hiểu sai - không có gì sai khi gói một container STL nếu bạn cần thêm các tính năng, nhưng chỉ cho "tên hàm thành viên nhất quán"? Quá nhiều chi phí. Đã mất quá nhiều thời gian để không có ROI.

Tôi nên thêm: Quy ước đặt tên không nhất quán chỉ là thứ bạn sống khi làm việc với C++. Có quá nhiều phong cách khác nhau trong quá nhiều thư viện có sẵn (và hữu ích).

0

Không được quấn chúng như thế.
Bạn bọc các thùng chứa nếu bạn muốn thay đổi các thùng chứa.
Ví dụ: nếu bạn có unordered_map chỉ nên thêm/xóa phần tử trong nội bộ nhưng nên hiển thị toán tử [] mà bạn quấn nó lên và tạo [] của riêng bạn để hiển thị [] của hộp chứa bên trong và bạn cũng hiển thị bộ lọc const_iterator const_iterator của unordered_map.

2

... có khả năng trao đổi việc thực hiện mà không sửa đổi mã khách hàng

Ở cấp độ giải quyết vấn đề mã, bạn có thể muốn chọn một container nội bộ khác nhau trong khi trình bày các API tương tự. Tuy nhiên, bạn không thể có mã hợp lý để chọn một Danh sách, sau đó trao đổi việc triển khai cho bất kỳ thứ gì khác mà không ảnh hưởng đến đặc tính hiệu năng và bộ nhớ đã dẫn đến mã máy khách để chọn danh sách để bắt đầu. Các thỏa hiệp được thực hiện trong các thùng chứa tiêu chuẩn được hiểu rõ và được ghi chép đầy đủ ...nó không có giá trị giảm giá giáo dục của nhân viên lập trình của bạn, tài liệu tham khảo có sẵn, tăng thời gian cho nhân viên mới để có được lên đến tốc độ vv chỉ để có một wrapper homegrown.

2

Bất kỳ trình biên dịch nào cũng có thể tạo các lớp được bao bọc nhanh như các lớp chuẩn, tuy nhiên các lớp của bạn có thể ít sử dụng được trong các trình gỡ rối và các công cụ khác có thể chuyên dùng cho các thùng chứa tiêu chuẩn. Ngoài ra, bạn có thể sẽ có thông báo lỗi về các lỗi biên dịch thời gian phức tạp hơn một chút so với những lỗi đã được lập trình mà một lập trình viên nhận được khi phạm sai lầm khi sử dụng mẫu.

Quy ước đặt tên của bạn không phù hợp với tôi hơn bất kỳ chuẩn nào; nó thực sự IMO một chút tồi tệ hơn và hơi nguy hiểm; ví dụ. nó sử dụng cùng một CamelCasing cho cả hai lớp và phương pháp và làm cho tôi băn khoăn liệu bạn có biết những hạn chế áp đặt bởi tiêu chuẩn trên một tên như _list ...

Hơn nữa quy ước đặt tên của bạn chỉ được bạn biết và có thể là một vài những người khác, thay vào đó là một tiêu chuẩn được biết đến bởi một số lượng lớn các lập trình viên C++ (bao gồm cả bạn và hy vọng những người khác). Và những gì về các thư viện bên ngoài bạn có thể cần phải thêm vào dự án của bạn? Bạn sẽ thay đổi giao diện của họ để các container tùy chỉnh của bạn được sử dụng thay cho các container tiêu chuẩn?

Vì vậy, tôi tự hỏi, đâu là điểm cộng của việc sử dụng quy ước đặt tên tùy chỉnh của bạn? Tôi chỉ thấy các nhược điểm ...

+0

Theo như tôi biết, tiêu chuẩn C++ chỉ dự trữ các tên bắt đầu bằng dấu gạch dưới trong không gian tên chung. [link] (http://stackoverflow.com/q/228783/509880) Làm cho các thông báo lỗi mẫu khó hiểu thậm chí còn khó hiểu hơn là một điểm rất tốt. – PkmX

+0

Đây chính là giới hạn mà tôi đã nói đến. Như tôi đã nói trước đó việc sử dụng đó là hợp pháp nhưng IMO nguy hiểm vì tính hợp lệ phụ thuộc vào ngữ cảnh. Đặt dấu gạch dưới ở cuối là IMO một ý tưởng tốt hơn. – 6502

3

Âm thanh như một công việc cho typedef, không phải là trình bao bọc.

+0

Hoàn toàn đồng ý. Trong thực tế, tôi luôn luôn sử dụng typedef bất cứ khi nào tôi sử dụng một container. Không chỉ làm cho nó dễ dàng hơn (dễ dàng hơn, không tự động) để thay đổi các thùng chứa sau này, mà còn để ngăn chặn lặp lại toàn bộ định nghĩa std :: ... lặp đi lặp lại (đặc biệt là đối với các trình vòng lặp, mặc dù điều này ít hơn từ khóa tự động). – Patrick

1

Đóng gói các hộp chứa STL để làm cho chúng an toàn chỉ, để ẩn khỏi chi tiết triển khai người dùng, để cung cấp cho người dùng chức năng giới hạn ... đây là những lý do hợp lệ.

Chỉ gói một vì nó không tuân theo quy ước vỏ của bạn là một sự lãng phí thời gian và tiền bạc và có thể gây ra lỗi. Chỉ cần chấp nhận rằng STL sử dụng tất cả các chữ thường.

0

Trong thư trả lời để chỉnh sửa của bạn: Tôi đề nghị bạn có một cái nhìn tại Adaptor Design Pattern:

Chuyển đổi giao diện của một lớp thành khác giao diện khách hàng mong đợi. Bộ điều hợp cho phép các lớp hoạt động cùng nhau mà không thể khác vì giao diện không tương thích. (. Thiết kế Patterns, E. Gamma et Al)

Sau đó, bạn nên thay đổi quan điểm của bạn và sử dụng một từ vựng khác nhau: xem xét lớp học của bạn như là một dịch thay vì một wrapper và và thích thuật ngữ "bộ điều hợp".

Bạn sẽ có một lớp cơ sở trừu tượng mà định nghĩa một giao diện phổ biến dự kiến ​​ứng dụng của bạn, và một lớp con mới cho mỗi thực hiện cụ thể:

class ContainerInterface 
{ 
    virtual Iterator Find(...) = 0; 
}; 
class AVLAdapter : public ContainerInterface 
{ 
    virtual Iterator Find(...) { /* implement AVL */ } 
} 
class RBAdapter : public ContainerInterface 
{ 
    virtual Iterator Find(...) { /* implement RB tree */ } 
} 

Bạn có thể dễ dàng trao đổi giữa các thực hiện khác nhau:

ContainerInterface *c = new AVLAdapter ; 
c->Find(...); 
delete c; 
c = new RBAdapter ; 
c->Find(...); 

Và mẫu này có thể mở rộng: để kiểm tra triển khai mới, hãy cung cấp một lớp con mới. Mã ứng dụng không thay đổi.

class NewAdapter : public ContainerInterface 
{ 
    virtual Iterator Find(...) { /* implement new method */ } 
} 
delete c; 
c = new NewAdapter ; 
c->Find(...); 
Các vấn đề liên quan