2010-01-25 26 views
12

Tôi muốn viết các giao diện chức năng buộc người dùng phải thừa nhận ý nghĩa ngữ nghĩa của các hằng số dựng sẵn. Ví dụ, tôi muốn nhânCách tốt nhất để buộc người dùng hàm C++ xác nhận ý nghĩa ngữ nghĩa của tham số là hằng số là gì?

void rotate(float angle); // Rotate the world by an angle in radians. 

và thay đổi nó để

void rotate(Radians angle); 

Tôi có ngay trong tin rằng vấn đề với thực hiện một lớp radian là nó cho biết thêm mã và làm cho chương trình chậm hơn . Có cách nào tốt hơn để làm điều này?

+4

Việc sử dụng lớp 'Radian' bổ sung sẽ chỉ làm cho chương trình 'chậm hơn' nếu mã của bạn được viết kém. Hầu hết các trình biên dịch (đặc biệt là GCC) các lớp 'wrapper' nội tuyến đến mức không có mã nào cả. – LiraNuna

+2

@Phineas Nếu bạn định cung cấp "set/get" cho phao thì đừng đi! Trừ khi bạn sẽ ẩn đại diện của 'Radian', chỉ cần viết:' typedef float Radian; ';) – AraK

+7

@AraK, tôi nghĩ một trong những điều mà OP quan tâm là thực tế là tất cả những người gọi hàm sẽ không nhất thiết phải được cập nhật để sử dụng loại đơn vị mới và do đó sẽ âm thầm trở thành sai. Một typedef không khắc phục vấn đề này. – Omnifarious

Trả lời

21

Không, có thể tạo một lớp Radian cần được tối ưu hóa bởi hầu hết các trình biên dịch hợp lý vào một thứ không chậm hơn một float đơn giản. Bạn có thể quan tâm đến boost.units. Trong thực tế, với boost.units bạn thậm chí có thể thiết lập nó để nếu ai đó muốn vượt qua trong một góc độ, nó sẽ tự động được chuyển đổi sang radian trước khi được chuyển đến chức năng. Điều đó sẽ có thể làm chậm mọi thứ xuống một chút, nhưng nó sắp xếp nó để bạn có thể thay đổi những gì đơn vị một chức năng muốn mà không cần phải quay trở lại và chỉnh sửa một tấn toàn bộ mã.

Và khi cuối cùng bạn muốn chuyển và chỉnh sửa tất cả mã, bạn có thể tạm thời sửa lỗi để chuyển đổi không hoạt động và để trình biên dịch tìm thấy nó cho bạn.

Có thể làm cho trình biên dịch thực thi các ràng buộc như thế này cho bạn mà không có hình phạt thời gian chạy và thậm chí có thể viết tất cả mã chuyển đổi cho bạn (một hình phạt thời gian rất nhỏ) là một trong những điều thực sự gọn gàng về C++ và làm cho nó xứng đáng với sự phức tạp thêm trên C.

đây là một phiên bản thực sự đơn giản về những gì lớp này có thể trông như thế nào nếu bạn tay mã hoá nó:

#include <cmath> 

// This class is tiny enough because it has no virtual functions and only one 
// data member that it's likely more efficient to pass by value than by 
// reference. 
class Radians { 
public: 
    // If you don't put in explicit, the compiler will automatically convert a 
    // float to a Radians for you and undo all of the hard work you did to make 
    // sure callers express their intentions. 
    explicit Radians(float radians) : value_(radians) {} 

    float getValue() const { return value_; } 
    void setValue(float radians) { value_ = radians; } 

    float asDegrees() const { return value_ * 180/M_PI; } 

    // This allows you to say Radians r = Radians::fromDegrees(something); 
    static Radians fromDegrees(float degrees) { 
     return Radians(degrees * M_PI/180); 
    } 

private: 
    float value_; 
}; 

Thông báo như thế nào tất cả các chức năng được khai báo trong cơ thể lớp. Điều này làm cho tất cả chúng hoàn toàn có từ khóa inline. Một trình biên dịch tốt sẽ tối ưu hóa tất cả các chức năng đó ra khỏi sự tồn tại. Tất nhiên, các chức năng chuyển đổi sẽ tạo ra mã để thực hiện chuyển đổi, nhưng nếu không nó sẽ giống như có một phao trần.

+0

@Nikolai N Fetissov, Rất tiếc. Cảm ơn bạn đã chỉnh sửa. :-) – Omnifarious

+2

+1 vì không quên nhận xét về hàm tạo rõ ràng. – log0

5

Đây là cách hoàn hảo để thực hiện việc này. Đó là những gì làm cho C thành C++. Sử dụng các lớp mà bạn cần chúng và đóng gói dữ liệu. Nó không làm cho chương trình nào đáng chú ý chậm hơn, vì trình biên dịch thực hiện tất cả các tối ưu hóa.

+0

Với lớp nội tuyến mỏng, không có bất kỳ chi phí thực nào. Và, vâng, gói các giá trị nguyên thủy theo cách này là một ý tưởng tuyệt vời. –

+3

Tôi đề cập đến boost.units bởi vì nó sẽ làm rất nhiều công việc cho bạn. – Omnifarious

0

Chậm hơn là con mắt của người xem. Bạn gọi mã này bao lâu một lần? Có, bạn sẽ tạo các mục lớn hơn trên stack khi chuyển một lớp Radian hơn là một float nguyên thủy.

Tại sao điều này quan trọng? Tại một thời điểm nào đó, người dùng trong lớp của bạn phải có một nửa bộ não và hiểu được API. Bạn có thể khai báo nó như

void rotate(float radians); 

Cách khác, giả sử bạn không thực sự thay đổi góc, bạn có thể vượt qua lớp radian của bạn bằng cách tham chiếu const:

void rotate(const Radians &angle); 

mà có lẽ sẽ nhanh hơn đi ngang qua giá trị .

+1

Tại sao lớp radian nên lớn hơn một float nguyên thủy? – UncleBens

+3

Lớp học có một thành viên 'float' lớn hơn một' float' trong cách sử dụng thực tế như thế nào? – Bill

+0

+1 cho xoay (radian float) nó là đơn giản, nó không giới thiệu loại không cần thiết như Radian là có thể nâng cao câu hỏi mới, và tên của tham số không thể làm cho chức năng rõ ràng hơn và tự mô tả - rõ ràng là radian có nghĩa là góc. Nguyên tắc KISS được áp dụng. – mloskot

1

Với các phiên bản đơn giản nhất (typedef, một lớp có thuộc tính thành viên và trình truy cập nội tuyến) trình biên dịch có thể làm cho nó nhanh như phiên bản float đơn giản.

Cũng xem xét sử dụng một số thư viện lập trình meta như boost.units.

+1

Typedef không an toàn. –

+1

Đúng, nhưng nó cung cấp cho người đọc ngữ cảnh về ý nghĩa của biến số hoặc phạm vi mà nó có. –

3

Nó không cần phải chậm hơn.

Thư viện Ogre3d có lớp Radian. Việc lắp ráp được tạo ra là chính xác giống như của việc sử dụng một phao trực tiếp (ít nhất khi tôi thử nghiệm cuối cùng dưới gcc với tối ưu hóa kích hoạt)

6

Chìa khóa để hạn chế này là để khai báo constructor của bạn rõ ràng:

class Radians 
{ 
public: 
    explicit Radians(float value) : m_value(value) {} 
private: 
    float m_value; 
}; 

Đó cách người dùng của bạn không thể nhập rotate(4.5). Họ sẽ phải nhập rotate(Radians(4.5)).

2

Hoàn toàn có thể viết mã OOP hiệu quả như mã thủ tục. Thậm chí nhiều hơn, đôi khi bạn có thể tăng tốc đáng ngạc nhiên nếu bạn tận dụng lợi thế của ngữ nghĩa bổ sung hoặc mã dựa trên lớp học. Một trong những ví dụ tuyệt vời về lợi thế như vậy là std::swap. Có, sử dụng mẫu;).

Điều gì làm cho nó có thể viết một lớp Radian hiệu quả là nội tuyến. Tuy nhiên, sử dụng nó một cách khôn ngoan!

Checklist:

  • đọc về hiệu quả nội tuyến
  • chăm sóc để có một constructor explicit
  • chăm sóc để sử dụng đúng từ khóa const khi áp dụng
  • đọc về Optimizing C++
  • cuối cùng, nếu bạn thực hiện rất nhiều biểu thức liên quan đến lớp học của mình, hãy tăng lên khoảng Tem biểu thức tấm
Các vấn đề liên quan