Phong cách mã hóa cuối cùng là chủ quan, và rất khó có khả năng lợi ích hiệu suất đáng kể sẽ đến từ nó. Nhưng đây là những gì tôi muốn nói rằng bạn có được từ việc sử dụng tự do khởi tạo thống nhất:
Giảm thiểu Typenames dư thừa
Hãy xem xét những điều sau đây:
vec3 GetValue()
{
return vec3(x, y, z);
}
Tại sao tôi cần gõ vec3
hai lần? Có một điểm cho điều đó? Trình biên dịch biết tốt và tốt những gì hàm trả về. Tại sao tôi không thể nói, "hãy gọi cho nhà xây dựng về những gì tôi trả lại với các giá trị này và trả lại nó?" Với khởi tạo thống nhất, tôi có thể:
vec3 GetValue()
{
return {x, y, z};
}
Làm tất cả mọi việc.
Thậm chí tốt hơn là cho các đối số chức năng. Xem xét điều này:
void DoSomething(const std::string &str);
DoSomething("A string.");
Điều đó hoạt động mà không cần phải gõ một kiểu chữ, bởi vì std::string
biết cách tự xây dựng từ một const char*
cách ngầm định. Thật tuyệt. Nhưng nếu chuỗi đó đến từ đâu, hãy nói RapidXML. Hoặc một chuỗi Lua. Đó là, giả sử tôi thực sự biết chiều dài của chuỗi lên phía trước. Hàm std::string
tạo có một const char*
sẽ phải lấy độ dài của chuỗi nếu tôi chỉ cần vượt qua a const char*
.
Có một quá tải mà có một chiều dài rõ ràng mặc dù. Nhưng để sử dụng nó, tôi phải làm điều này : DoSomething(std::string(strValue, strLen))
. Tại sao có thêm tên chữ trong đó? Trình biên dịch biết loại này là gì. Cũng giống như với auto
, chúng ta có thể tránh có thêm các kiểu chữ:
DoSomething({strValue, strLen});
Nó chỉ hoạt động. Không đánh máy, không ồn ào, không có gì. Trình biên dịch thực hiện công việc của nó, mã ngắn hơn và mọi người đều vui vẻ.
Cấp, có những lập luận được đưa ra rằng phiên bản đầu tiên ( DoSomething(std::string(strValue, strLen))
) dễ đọc hơn. Đó là, rõ ràng những gì đang xảy ra và ai đang làm gì. Điều đó đúng, ở một mức độ nào đó; hiểu mã dựa trên khởi tạo thống nhất đòi hỏi phải xem xét nguyên mẫu hàm. Đây là cùng một lý do tại sao một số người nói rằng bạn không bao giờ nên chuyển tham số bằng tham chiếu không phải là const: để bạn có thể thấy tại trang web cuộc gọi nếu một giá trị đang được sửa đổi.
Nhưng điều tương tự có thể được nói cho auto
; biết những gì bạn nhận được từ auto v = GetSomething();
yêu cầu nhìn vào định nghĩa của GetSomething
. Nhưng điều đó đã không ngừng auto
được sử dụng với sự từ bỏ gần như liều lĩnh một khi bạn có quyền truy cập vào nó. Cá nhân, tôi nghĩ rằng nó sẽ ổn khi bạn đã quen với nó. Đặc biệt với một IDE tốt.
Không bao giờ có được phân tích khó chịu nhất
Đây là một số mã.
class Bar;
void Func()
{
int foo(Bar());
}
Pop đố: là foo
gì? Nếu bạn trả lời "một biến", bạn đã sai. Đây thực sự là nguyên mẫu của một hàm lấy tham số của nó là hàm trả về a Bar
và foo
giá trị trả về của hàm là int.
Đây được gọi là "Phân tích phật ý nhất" của C ++ bởi vì nó hoàn toàn không có ý nghĩa gì với con người. Nhưng đáng buồn là các quy tắc của C ++ yêu cầu điều này: nếu nó có thể được hiểu là một nguyên mẫu hàm, thì nó sẽ như vậy. Vấn đề là Bar()
; đó có thể là một trong hai điều Nó có thể là một loại có tên Bar
, có nghĩa là nó đang tạo tạm thời. Hoặc nó có thể là một hàm không có tham số và trả về a Bar
.
Khởi tạo thống nhất không thể được hiểu là một nguyên mẫu hàm:
class Bar;
void Func()
{
int foo{Bar{}};
}
Bar{}
luôn luôn tạo ra một tạm thời int foo{...}
luôn tạo ra một biến.
Có nhiều trường hợp bạn muốn sử dụng Typename()
nhưng đơn giản là không thể vì quy tắc phân tích cú pháp của C ++. Với Typename{}
, không có sự mơ hồ.
Những lý do không nên
Sức mạnh thực sự duy nhất bạn từ bỏ là thu hẹp. Bạn không thể khởi tạo một giá trị nhỏ hơn với giá trị lớn hơn với khởi tạo thống nhất.
int val{5.2};
Điều đó sẽ không được biên dịch. Bạn có thể làm điều đó với khởi tạo kiểu cũ, nhưng không khởi tạo thống nhất.
Điều này đã được thực hiện một phần để làm cho danh sách khởi tạo thực sự hoạt động. Nếu không, sẽ có rất nhiều trường hợp mơ hồ liên quan đến các loại danh sách khởi tạo.
Tất nhiên, một số người có thể lập luận rằng mã như vậy xứng đáng để không biên dịch. Cá nhân tôi tình cờ đồng ý; thu hẹp rất nguy hiểm và có thể dẫn đến hành vi khó chịu. Có lẽ tốt nhất là bắt những vấn đề đó sớm ở giai đoạn biên dịch. Ít nhất, thu hẹp cho thấy ai đó không suy nghĩ quá nhiều về mã.
Lưu ý rằng trình biên dịch nói chung sẽ cảnh báo bạn về loại điều này nếu mức cảnh báo của bạn cao. Vì vậy, thực sự, tất cả những điều này là làm cho cảnh báo thành một lỗi bắt buộc. Một số có thể nói rằng dù sao bạn cũng nên làm điều đó;)
Có một lý do khác để không:
std::vector<int> v{100};
Cái này làm gì Nó có thể tạo ra một vector<int>
trăm mục được xây dựng mặc định. Hoặc nó có thể tạo ra vector<int>
với 1 mục giá trị của ai 100
. Cả hai về mặt lý thuyết đều có thể.
Trong thực tế, nó làm sau này.
Tại sao? Danh sách khởi tạo sử dụng cú pháp giống như khởi tạo thống nhất. Vì vậy, phải có một số quy tắc để giải thích những gì cần làm trong trường hợp mơ hồ. Quy tắc này khá đơn giản: nếu trình biên dịch có thể sử dụng hàm tạo danh sách khởi tạo với danh sách khởi tạo cú đúp, thì nó sẽ . Vì vector<int>
có một hàm tạo danh sách khởi tạo initializer_list<int>
, và {100} có thể là hợp lệ initializer_list<int>
, do đó nó phải như vậy .
Để có được hàm tạo kích thước, bạn phải sử dụng ()
thay vì {}
.
Lưu ý rằng nếu đây là một vector
thứ không thể chuyển đổi thành số nguyên, thì điều này sẽ không xảy ra. Trình khởi tạo_list sẽ không phù hợp với trình tạo danh sách trình khởi tạo của vector
loại đó và do đó trình biên dịch sẽ được tự do chọn từ các hàm tạo khác.