Từ 1 tới 5 trên tổng số 5 kết quả

Đề tài: Lập trình template với C++ qua ví dụ cụ thể

  1. #1
    Ngày gia nhập
    07 2011
    Bài viết
    160

    Mặc định Lập trình template với C++ qua ví dụ cụ thể

    beautifulsoul84hung đang tiếp tục công việc dở dang của rox_rook: viết 1 tutorial về lập trình template với C++. Học đi đôi với hành, mình mạn phép làm 1 đề tài làm 1 ví dụ cụ thể ứng dụng các kiến thức về template. Công việc sẽ khá dài, mình sẽ viết làm nhiều phần, và mình sẽ cố để áp dụng được nhiều kiến thức cơ bản về template trong đó. Welcome các bạn tham gia thảo luận đưa ra giải pháp trong lúc mình chưa kịp post phần tiếp theo

    Ý tưởng: xây dựng một hàm cast sử dụng xuyên suốt hệ thống với bất kỳ 2 kiểu A, B nào tương thích:

    C++ Code:
    1. B b;
    2. A a = cast<A>(b);

    Ngoài ra ta còn xây dựng hàm 2 đối số assign(a, b) có tác dụng tương đương a = cast<A>(b). Trong một số trường hợp (nhất là khi biến a đã có từ trước) thì sử dụng assign có 2 lợi thế so với cast:
    - Không phải tạo biến trung gian để trả về (Hiệu quả cao hơn)
    - Khi gọi hàm cast thì phải trực tiếp chỉ ra kiểu trả về (cast<A>(b)), do compiler chỉ tự suy ra được kiểu của b. Còn khi gọi hàm assign thì do có 2 đối số, compiler tự suy luận được cả 2 kiểu. Vì vậy lệnh gọi hàm assign nhìn như 1 hàm bình thường: assign(a, b). Lợi thế này sẽ được nêu rõ ở post sau.

    Liệt kê tạm 1 số trường hợp sử dụng:
    - Cast giữa 2 kiểu số: int i = cast<int>(10.7);
    - Cast số sang sang chuỗi và ngược lại: std::string s = cast<std::string>(1.234);
    - Cast std::vector sang kiểu my_vector tự xây dựng nào đó và ngược lại.
    - Cast my_vector về my_matrix, thành ma trận nx1 với vector n phần tử ...v.v
    - Cast từ const char* sang std::string. Chuyển đổi giữa các loại string: std::string, CString (MFC), QString (Qt), ..
    - Các bạn nghĩ ra trường hợp sử dụng nào nữa mà code hiện tại không đáp ứng được thì cứ nêu ra, mình sẽ tìm cách giải quyết!

    Kết quả của việc xây dựng hàm cast (và assign) như trên: được 1 hàm thống nhất cho việc chuyển đổi các kiểu dữ liệu. Không còn phải quan tâm đến các hàm khác nhau như atoi, atof, itoa,.. tất cả chỉ cần 1 hàm cast.

    Khai báo của 2 hàm đó sẽ là:
    C++ Code:
    1. template<class A, class B> void assign(A& a, const B& b);
    2. template<class A, class B> A cast(const B& b)
    3. {
    4.   A a;
    5.   assign(a, b);
    6.   return a;
    7. }
    Chú ý: bạn có thể dùng template<class A, class B> hoặc template<typename A, typename B> hoặc 1 cái class, 1 cái typename đều như nhau.

    Hàm cast mặc định sẽ gọi hàm assign. Đây là trường hợp mặc định, compiler sẽ chỉ chọn hàm này khi không có 1 phiên bản specialization nào khác phù hợp hơn. Các trường hợp riêng ta có thể tạo specialization riêng để xử lý, còn lại thì ta chỉ phải cài đặt hàm assign là đủ

    Ý tưởng đầu tiên: Chuyển từ B sang A qua kiểu chuỗi trung gian (sử dụng std::stringstream)
    C++ Code:
    1. #include <sstream>
    2. template<class A, class B>
    3. void assign(A& a, const B& b) {
    4.   std::stringstream ss;
    5.   ss << b;
    6.   ss >> a;
    7. }

    Cách làm này đã đáp ứng được cho hầu hết các loại dữ liệu cơ bản (nhưng có nhiều hạn chế, sẽ nói sau). Nhưng chương trình của tôi sử dụng rất nhiều ma trận và vector!! Một trong những nhu cầu cấp thiết của tôi là cần chuyển đổi giữa std::vector<X> và my_vector<Y>, với my_vector là class của tôi, X và Y là các kiểu số (nguyên, thực, phức). Trong khi đó std::vector lại không hỗ trợ operator >><<. Tôi cần viết 1 hàm chuyên biệt (specialization) cho trường hợp này, để cứ khi nào gặp lệnh assign(a, b) mà a là kiểu my_vector<X>, b là kiểu std::vector<Y> thì compiler sẽ dùng hàm assign riêng đó. Các trường hợp khác vẫn dùng hàm assign mặc định. Và do mặc định (lại mặc định) cast() sẽ gọi assign() nên tôi có thể viết code như sau:

    C++ Code:
    1. std::vector<int> v2;
    2. my_vector<double> v1 = cast<my_vector<double> >(v2);

    Chú ý: 2 dấu > phải viết cách nhau. Nếu viết liền compiler sẽ hiểu lầm thành toán tử >> và báo lỗi.

    Vậy cần viết specialization để xử lý trường hợp này thế nào?

    Phù, mệt quá, nghỉ đã. Mong các bạn vào góp ý và đưa ra giải pháp cho topic thêm xôm và tôi cũng có thêm hứng thú hoàn thành nốt tutorial Chú ý đây là topic về sử dụng template chứ không phải về thuật toán, nên nếu bạn đưa ra giải pháp thì chỉ cần đưa khai báo hàm (declaration) là đủ. Không cần quan tâm đến việc implement.

  2. #2
    Ngày gia nhập
    12 2010
    Nơi ở
    Hà Nội
    Bài viết
    137

    Mặc định DingPhonh

    Có nên kiểm tra lỗi của thằng >> không nhỉ? Em xin đề xuất:
    C++ Code:
    1. template<class A, class B>
    2. void assign(A& a, const B& b) {
    3.   std::stringstream ss;
    4.   A c;
    5.   ss << b;
    6.   if ( (ss >> c) )
    7.     a = c;
    8.     else a = A();
    9. }
    Freelancer, free coder, free tester.
    Nhận hợp tác viết app, viết midlet khi free time.
    Tình trạng: Siêu bận!

  3. #3
    Ngày gia nhập
    07 2011
    Bài viết
    160

    Cảm ơn DingPhonh đã góp ý. Code thực tế thì ta sẽ phải quan tâm đến nhiều vấn đề hơn và kiểm tra lỗi kỹ càng. Tuy nhiên ở trong phạm vi topic này, mình tập trung vào việc sử dụng template - tức là chủ yếu là khai báo hàm. Code implementation, nếu có, cũng chỉ mang tính chất minh họa.

    Nhắc lại, mục tiêu của chúng ta trong tutorial này là xây dựng 1 hàm cast và 1 hàm assign sử dụng xuyên suốt hệ thống với bất kỳ 2 kiểu dữ liệu A, B nào tương thích (tức là về ngữ nghĩa B có thể chuyển sang A được). Mình sẽ dần dần đưa thêm requirement vào và sửa lại template declaration để có thể đáp ứng được requirement đó. Các bạn có thể thêm requirement - miễn là trong phạm vi cast và assign. Vì code của mình chắc chắn không phải là tốt nhất, nên rất mong các bạn góp ý thêm và đưa ra giải pháp riêng. Hoặc cũng có thể coi những requirement được đưa ra như những "câu đố" và giải câu đố cho thêm phần hứng thú. Thanks

    Chúng ta tạm thời không quan tâm đến cài đặt code (implementation). Chỉ tập trung vào phần khai báo các hàm template thế nào để đáp ứng được các yêu cầu đưa ra.

    Requirement mình đưa ra cuối post trước là: Xử lý riêng cho trường hợp cast từ std::vector<X> sang my_vector<Y>
    Đáp án của mình:
    C++ Code:
    1. template<class T1, class T2>
    2. void assign(my_vector<T1>& v1, const std::vector<T2>& v2) {..implement..}

    Và toàn bộ code cho đến thời điểm này sẽ là:
    C++ Code:
    1. template<class A, class B>
    2. void assign(A& a, const B& b) { // [1]
    3.     a = b;
    4. }
    5.  
    6. template<class A, class B>
    7. A cast(const B& b) {
    8.     A a;
    9.     assign(a, b);
    10.     return a;
    11. }
    12.  
    13. template<class T1, class T2>
    14. void assign(my_vector<T1>& v1, const std::vector<T2>& v2) {..implement..} // [2]
    15. template<class T1, class T2>
    16. void assign(std::vector<T1>& v1, const my_vector<T2>& v2) {..implement..} // [3]

    Note: Mình đã thay trường hợp mặc định của assign là dùng toán tử (=) cho đơn giản và đúng ý nghĩa của assign (việc implement các code chỉ mang tính chất minh họa)
    Với code trên, trường hợp assign hay cast giữa std::vector<X> và my_vector<Y> đã được xử lý riêng (với X, Y là 2 kiểu bất kỳ). Cứ cast/assign giữa std::vector và my_vector thì compiler sẽ dùng overloaded version mới đưa vào ([2] hoặc [3] tương ứng với 2 chiều cast), các trường hợp khác sẽ dùng mặc định ([1])
    Vì sao? Vì compiler biết [2] và [3] là trường hợp hẹp hơn của [1]; [1] là trường hợp chung hơn. Và nó sẽ biết tự chọn template version nào hẹp nhất đáp ứng được đầu vào.

    Bây giờ mình đưa ra requirement mới:
    Code hiện tại không cast được sang std::string. Xử lý riêng cho trường hợp cast từ kiểu X bất kỳ sang std::string (hoặc assign(a, b) với a là kiểu std::string, b là kiểu bất kỳ). Không quan tâm đến việc implement nhé!
    Hic hic, các bạn vào đóng góp ý tưởng đi mà. Năn nỉ đấy

    Edit: Mình đã dùng nhầm cụm từ "specialization". 2 hàm template [2] và [3] là 2 hàm template độc lập với [1] chứ không phải specialization của [1]. Compiler sẽ biết chọn overloaded version nào hẹp nhất. 1 function F được coi là hẹp hơn (more specialized - mình tạm dịch là hẹp hơn) function G khi mọi input hợp lệ của F đều là input hợp lệ của G nhưng chiều ngược lại thì không đúng.
    Đã được chỉnh sửa lần cuối bởi fbchicken : 17-09-2011 lúc 02:59 PM.

  4. #4
    Ngày gia nhập
    12 2010
    Nơi ở
    Hà Nội
    Bài viết
    137

    Mặc định DingPhonh

    Thử b với kiểu int, float, char thì thằng assign ở trên đã đủ cast sang std::string rồi mà. Nhưng nếu chơi vector cast sang string thì
    C++ Code:
    1. template<class T1, class T2>
    2. void assign(std::string& s, const T1& b) {..implement..} // [4]
    Freelancer, free coder, free tester.
    Nhận hợp tác viết app, viết midlet khi free time.
    Tình trạng: Siêu bận!

  5. #5
    Ngày gia nhập
    07 2011
    Bài viết
    160

    Trích dẫn Nguyên bản được gửi bởi DingPhonh Xem bài viết
    Thử b với kiểu int, float, char thì thằng assign ở trên đã đủ cast sang std::string rồi mà. Nhưng nếu chơi vector cast sang string thì
    C++ Code:
    1. template<class T1, class T2>
    2. void assign(std::string& s, const T1& b) {..implement..} // [4]
    Code của bạn đúng rồi, nhưng do tham số T2 của bạn không được sử dụng trong đầu vào function nên bạn sẽ phải viết trực tiếp các tham số template ra, ví dụ: assign<int, void>(s, 123);. Do tham số T2 không được sử dụng nên để là bất kỳ cái gì đều được, mình để là void

    Để có thể gọi assign(s, 123) thì bạn bỏ bớt 1 tham số đi:
    [CODE=C++]template<class T>
    void assign(std::string& s, const T& b) {..implement..} // [4]

    Đây lại là 1 overloaded version nữa. Chúng ta vẫn chưa tạo 1 template specialization nào và nói thực thì không cần thiết tạo template specialization đối với function template. 1 specialization cho function assign trên sẽ có dạng như sau:

    C++ Code:
    1. template<> void assign<int, double>(int& a, const double& b) {..implement(4a)..}
    ==> không cần thiết lắm vì bạn có thể tạo 1 hàm (không template) vẫn có tác dụng tương tự:
    C++ Code:
    1. void assign(int& a, const double& b) {..implement(4b)..}

    Có 1 khác biệt nhỏ giữa 2 cách trên: Nếu bạn có cả 4a và 4b trong chương trình thì khi gọi assign(i, 1.0) chẳng hạn compiler sẽ ưu tiên hàm không template (4b) - Khi không có 4b nó mới gọi 4a. Nhưng khi bạn gọi assign<>(i, 1.0) thì 2 dấu <> sẽ cho compiler biết chỉ chọn hàm template - vì vậy compiler không bao giờ gọi 4b, nếu không có 4a thì compiler sẽ dùng hàm mặc định [1]. Nhưng trong phần lớn các trường hợp thì tạo specialization cho function là không cần thiết vì chúng ta có thể dùng cách 4b

    Xin lỗi các bạn vì đã hơi lan man. Về specialization khi nào viết đến class template mình sẽ nói kỹ sau.

    Chốt về lý thuyết: 1. Khi có nhiều template function cùng tên, compiler sẽ chọn function nào có miền đầu vào hẹp nhất (most specialized - xem post trước) mà phù hợp với kiểu của các đối số đầu vào (parameters).

    2. Sẽ rất ít khi bạn cần tạo specialization cho function template. Các template function cùng tên không phải specialization của nhau. Có 1 cách đơn giản để phân biệt: Khi ngay sau tên hàm/lớp là dấu < (trong cặp < ... > của template) thì nó là 1 specialization của 1 hàm/lớp được khai báo trước đó.


    Mình đang bí 2 vấn đề :( mong bạn nào ra tay giúp đỡ:
    1. Dịch ra tiếng Việt chữ specialization thế nào cho hợp lý
    2. Mình cần 1 requirement đơn giản, có ý nghĩa cho thấy sự cần thiết phải chuyển sang class template với partial specialization.

    Trong khi chờ các bạn giúp đỡ thì mình đưa ra 1 requirement mới khá là ít ý nghĩa thực tế như sau:
    Viết hàm xử lý riêng cho assign(A& a, const B& b) với B là 1 iterator của 1 std::vector<A>
    Cụ thể hàm đó sẽ xử lý cho trường hợp sau: (có thể thay int bằng 1 kiểu bất kỳ)
    C++ Code:
    1. int i;
    2. std::vector<int> vi;
    3. ... gán vi = vector [1, 2, 3] ...
    4. assign(i, vi.begin()); // sẽ gán i = 1  là phần tử đầu tiên của vector - do vi.begin trỏ đến phần tử đầu tiên

Các đề tài tương tự

  1. Lập trình C++ template là gì? sử dụng template như thế nào?
    Gửi bởi dangvanhoavc trong diễn đàn Thắc mắc lập trình C/C++/C++0x
    Trả lời: 5
    Bài viết cuối: 16-01-2013, 09:11 PM
  2. Kỹ thuật C++ Template Method có liên quan gì đến template trong C++ ko?
    Gửi bởi ten_truycap trong diễn đàn Thắc mắc lập trình C/C++/C++0x
    Trả lời: 2
    Bài viết cuối: 22-10-2010, 11:26 PM
  3. Cách xây dựng và sử dụng template và mảng template trong C++?
    Gửi bởi nhatnha trong diễn đàn Thắc mắc lập trình C/C++/C++0x
    Trả lời: 4
    Bài viết cuối: 03-08-2010, 06:24 PM
  4. template
    Gửi bởi wawa trong diễn đàn Nhập môn lập trình C/C++
    Trả lời: 6
    Bài viết cuối: 10-01-2009, 07:36 PM
  5. Cho em hỏi về Template
    Gửi bởi piglet trong diễn đàn Nhập môn lập trình C/C++
    Trả lời: 4
    Bài viết cuối: 23-05-2008, 08:41 AM

Quyền hạn của bạn

  • Bạn không thể gửi đề tài mới
  • Bạn không thể gửi bài trả lời
  • Bạn không thể gửi các đính kèm
  • Bạn không thể chỉnh sửa bài viết của bạn